feat: 功能优化
This commit is contained in:
@@ -162,7 +162,21 @@ export const MaterialService = {
|
||||
return http.get(`${BASE_URL}/preview-url`, {
|
||||
params: { id: fileId, type }
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新文件显示名称
|
||||
* @param {number} fileId - 文件编号
|
||||
* @param {string} displayName - 新的显示名称
|
||||
* @returns {Promise}
|
||||
*/
|
||||
updateDisplayName(fileId, displayName) {
|
||||
return http.post(`${BASE_URL}/update-display-name`, {
|
||||
fileId,
|
||||
displayName
|
||||
})
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -42,6 +42,6 @@ import SidebarNav from '@/components/SidebarNav.vue'
|
||||
.content-scroll {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto; /* 右侧内容区域滚动 */
|
||||
padding: 16px 0px 16px 16px;
|
||||
padding: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -150,146 +150,6 @@ body { scrollbar-gutter: stable both-edges; }
|
||||
|
||||
|
||||
|
||||
/* ================================
|
||||
3. 组件样式 (Component Styles)
|
||||
================================ */
|
||||
|
||||
/* Button 组件 */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-button);
|
||||
font-weight: 500;
|
||||
transition: all 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: var(--space-2) var(--space-6);
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&--primary {
|
||||
background: var(--color-slate-900);
|
||||
color: white;
|
||||
box-shadow: var(--shadow-lg);
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: var(--color-slate-800);
|
||||
}
|
||||
}
|
||||
|
||||
&--secondary {
|
||||
background: white;
|
||||
color: var(--color-slate-700);
|
||||
border: 1px solid var(--color-border);
|
||||
padding: var(--space-1) var(--space-4);
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: var(--color-slate-50);
|
||||
}
|
||||
}
|
||||
|
||||
&--gradient {
|
||||
background: linear-gradient(to right, var(--color-indigo-600), var(--color-indigo-800));
|
||||
color: white;
|
||||
box-shadow: var(--shadow-blue);
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: linear-gradient(to right, var(--color-indigo-700), var(--color-indigo-900));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Input 组件 */
|
||||
.input {
|
||||
width: 100%;
|
||||
padding: var(--space-2) var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-button);
|
||||
font-size: 14px;
|
||||
transition: all 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
background: white;
|
||||
color: var(--color-text);
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-border-focus);
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--color-slate-400);
|
||||
}
|
||||
}
|
||||
|
||||
/* Card 组件 */
|
||||
.card {
|
||||
background: white;
|
||||
border: 1px solid var(--color-slate-200);
|
||||
border-radius: var(--radius-card);
|
||||
padding: var(--space-6);
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: all 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
|
||||
&:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
}
|
||||
|
||||
/* Table 组件 */
|
||||
.table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
|
||||
th {
|
||||
background: var(--color-slate-50);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
text-align: left;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--color-slate-500);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
border-bottom: 1px solid var(--color-slate-200);
|
||||
}
|
||||
|
||||
td {
|
||||
padding: var(--space-4);
|
||||
border-bottom: 1px solid var(--color-slate-100);
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background: var(--color-slate-50);
|
||||
}
|
||||
}
|
||||
|
||||
/* Tag 组件 */
|
||||
.tag {
|
||||
display: inline-block;
|
||||
padding: var(--space-0-5) var(--space-2);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
border-radius: var(--radius-tag);
|
||||
background: var(--color-gray-100);
|
||||
color: var(--color-slate-700);
|
||||
|
||||
&--red {
|
||||
background: #fee2e2;
|
||||
color: var(--color-red-800);
|
||||
}
|
||||
|
||||
&--yellow {
|
||||
background: #fef3c7;
|
||||
color: var(--color-yellow-600);
|
||||
}
|
||||
|
||||
&--vip {
|
||||
color: var(--color-yellow-500);
|
||||
border: 1px solid var(--color-yellow-500);
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +120,6 @@ function handleReset() {
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
color: var(--color-text);
|
||||
background: var(--color-bg);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s;
|
||||
|
||||
@@ -365,106 +365,151 @@ onMounted(async () => {
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
// ========== 公共变量 ==========
|
||||
@primary-color: var(--primary-color, #1677ff);
|
||||
@primary-bg-10: rgba(22, 119, 255, 0.1);
|
||||
@primary-bg-15: rgba(22, 119, 255, 0.15);
|
||||
@primary-bg-20: rgba(22, 119, 255, 0.2);
|
||||
@primary-bg-30: rgba(22, 119, 255, 0.3);
|
||||
@primary-bg-50: rgba(22, 119, 255, 0.5);
|
||||
@success-color: var(--color-green-500);
|
||||
@error-color: var(--color-red-500);
|
||||
@warning-color: var(--color-yellow-500);
|
||||
@surface-bg: var(--bg-primary);
|
||||
@surface-bg-25: var(--bg-primary);
|
||||
@surface-bg-30: var(--bg-secondary);
|
||||
@text-primary: var(--text-primary);
|
||||
@text-secondary: var(--text-secondary);
|
||||
@panel-bg: var(--bg-primary);
|
||||
|
||||
/* ========== 页面布局 ========== */
|
||||
.kling-page { padding: 0; }
|
||||
.kling-page {
|
||||
padding: 0;
|
||||
background: var(--bg-secondary);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.kling-content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24px;
|
||||
grid-template-columns: 480px 1fr;
|
||||
gap: 32px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.upload-panel,
|
||||
.result-panel {
|
||||
background: @panel-bg;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
|
||||
/* 优化左侧配置面板 */
|
||||
.upload-panel {
|
||||
background: var(--bg-primary);
|
||||
border-radius: 16px;
|
||||
padding: 28px;
|
||||
height: fit-content;
|
||||
position: sticky;
|
||||
top: 24px;
|
||||
max-height: calc(100vh - 48px);
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
|
||||
/* 自定义滚动条 */
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--border-light);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== 基础组件 ========== */
|
||||
.section { margin-bottom: 24px; }
|
||||
.section h3,
|
||||
.card-content h4,
|
||||
.audio-info h4 {
|
||||
color: @text-primary;
|
||||
.section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.section h3 {
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 12px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.section h3 { margin-bottom: 12px; }
|
||||
.card-content h4, .audio-info h4 { margin-bottom: 12px; font-size: 14px; }
|
||||
.card-content p, .duration-label span:first-child { color: @text-secondary; font-size: 13px; }
|
||||
.card-content p { margin: 0; }
|
||||
|
||||
.card-content h4,
|
||||
.audio-info h4 {
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.card-content p,
|
||||
.duration-label span:first-child {
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.card-content p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ========== 表单控件 ========== */
|
||||
.tts-textarea {
|
||||
background: var(--bg-primary);
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
color: @text-primary;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: 8px;
|
||||
color: var(--text-primary);
|
||||
|
||||
:deep(.ant-input) {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.text-hint {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-top: 8px;
|
||||
padding: 8px 12px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: 6px;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
padding: 12px 16px;
|
||||
background: rgba(var(--color-primary), 0.1);
|
||||
border: 1px solid rgba(var(--color-primary), 0.3);
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
color: @text-secondary;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.hint-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
.hint-icon { font-size: 14px; }
|
||||
|
||||
/* ========== 控制面板 ========== */
|
||||
.control-group { margin-bottom: 16px; }
|
||||
.control-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.control-label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: @text-primary;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.slider-card {
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: 8px;
|
||||
padding: 10px 12px;
|
||||
background: var(--bg-primary);
|
||||
padding: 12px 16px;
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.slider-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 12px;
|
||||
color: @text-secondary;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.slider-value {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: @text-primary;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.reset-btn {
|
||||
padding: 4px 12px;
|
||||
border: 1px solid var(--border-light);
|
||||
background: var(--bg-primary);
|
||||
color: @text-primary;
|
||||
color: var(--text-secondary);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
@@ -474,37 +519,43 @@ onMounted(async () => {
|
||||
.video-selection-cards {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.video-option-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 20px;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-primary);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--color-primary);
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
border-color: var(--color-primary);
|
||||
background: rgba(var(--color-primary), 0.1);
|
||||
}
|
||||
}
|
||||
.video-option-card:hover {
|
||||
border-color: @primary-color;
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
.video-option-card.selected {
|
||||
border-color: @primary-color;
|
||||
background: @primary-bg-10;
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
font-size: 24px;
|
||||
color: @primary-color;
|
||||
color: var(--color-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: @primary-bg-10;
|
||||
background: rgba(var(--color-primary), 0.1);
|
||||
border-radius: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ========== 视频预览 ========== */
|
||||
@@ -537,7 +588,7 @@ onMounted(async () => {
|
||||
bottom: 4px;
|
||||
right: 4px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: @text-primary;
|
||||
color: var(--text-primary);
|
||||
padding: 2px 4px;
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
@@ -548,7 +599,7 @@ onMounted(async () => {
|
||||
min-width: 0;
|
||||
}
|
||||
.video-title {
|
||||
color: @text-primary;
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
@@ -560,32 +611,81 @@ onMounted(async () => {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
font-size: 12px;
|
||||
color: @text-secondary;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* ========== 上传区域 ========== */
|
||||
.upload-zone {
|
||||
min-height: 300px;
|
||||
min-height: 280px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2px dashed var(--border-light);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-secondary);
|
||||
|
||||
&.drag-over {
|
||||
border-color: var(--color-primary);
|
||||
background: rgba(var(--color-primary), 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.upload-placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
color: @text-secondary;
|
||||
|
||||
h3 {
|
||||
color: var(--text-primary);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ant-btn {
|
||||
padding: 8px 24px;
|
||||
font-size: 14px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
.upload-placeholder h3 { color: @text-primary; }
|
||||
|
||||
.video-preview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: @text-primary;
|
||||
|
||||
p {
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
padding: 8px 16px;
|
||||
background: var(--bg-primary);
|
||||
border-radius: 6px;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-video {
|
||||
width: 100%;
|
||||
max-height: 300px;
|
||||
max-height: 280px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* ========== 验证结果 ========== */
|
||||
@@ -595,59 +695,93 @@ onMounted(async () => {
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-light);
|
||||
}
|
||||
|
||||
.validation-result.validation-passed {
|
||||
border-color: rgba(82, 196, 26, 0.3);
|
||||
background: rgba(82, 196, 26, 0.05);
|
||||
border-color: var(--color-success);
|
||||
background: rgba(var(--color-success), 0.05);
|
||||
}
|
||||
|
||||
.validation-result.validation-failed {
|
||||
border-color: rgba(255, 77, 79, 0.3);
|
||||
background: rgba(255, 77, 79, 0.05);
|
||||
border-color: var(--color-error);
|
||||
background: rgba(var(--color-error), 0.05);
|
||||
}
|
||||
|
||||
.validation-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 12px;
|
||||
padding: 12px;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 6px;
|
||||
}
|
||||
.status-icon { font-size: 18px; }
|
||||
|
||||
.status-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
color: @text-primary;
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ========== 时长对比进度条 ========== */
|
||||
.duration-comparison { margin-bottom: 16px; }
|
||||
.duration-bar { margin-bottom: 12px; }
|
||||
.duration-bar:last-child { margin-bottom: 0; }
|
||||
.duration-comparison {
|
||||
margin-bottom: 16px;
|
||||
padding: 12px;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.duration-bar {
|
||||
margin-bottom: 12px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.duration-label {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 6px;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.duration-value {
|
||||
color: @text-primary;
|
||||
color: var(--text-primary);
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
padding: 4px 8px;
|
||||
background: var(--bg-primary);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 8px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
background: var(--bg-primary);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
transition: width 0.3s;
|
||||
}
|
||||
|
||||
.audio-bar .progress-fill {
|
||||
background: linear-gradient(90deg, @primary-color, #60A5FA);
|
||||
background: var(--color-primary);
|
||||
}
|
||||
|
||||
.video-bar .progress-fill.success {
|
||||
background: linear-gradient(90deg, @success-color, #73d13d);
|
||||
background: var(--color-success);
|
||||
}
|
||||
|
||||
.video-bar .progress-fill.error {
|
||||
background: linear-gradient(90deg, @error-color, #ff7875);
|
||||
background: var(--color-error);
|
||||
}
|
||||
|
||||
/* ========== 错误提示 ========== */
|
||||
@@ -657,12 +791,17 @@ onMounted(async () => {
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: @error-color;
|
||||
color: var(--color-error);
|
||||
font-size: 13px;
|
||||
margin: 0 0 12px 0;
|
||||
}
|
||||
.quick-actions { display: flex; gap: 8px; }
|
||||
|
||||
.quick-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* ========== 音频生成 ========== */
|
||||
.audio-generation-section {
|
||||
@@ -672,24 +811,49 @@ onMounted(async () => {
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-light);
|
||||
}
|
||||
.generate-audio-row { margin-bottom: 16px; }
|
||||
|
||||
.generate-audio-row {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.audio-preview {
|
||||
padding: 16px;
|
||||
background: var(--bg-primary);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.duration-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.duration-info .label { color: @text-secondary; }
|
||||
.duration-info .value { color: @text-primary; font-weight: 600; }
|
||||
.duration-info.validation-passed .value { color: @success-color; }
|
||||
.duration-info.validation-failed .value { color: @error-color; }
|
||||
.audio-player { margin: 16px 0; }
|
||||
.audio-element { width: 100%; }
|
||||
|
||||
.duration-info .label {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.duration-info .value {
|
||||
color: var(--text-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.duration-info.validation-passed .value {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.duration-info.validation-failed .value {
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
.audio-player {
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.audio-element {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.regenerate-row {
|
||||
text-align: center;
|
||||
margin-top: 12px;
|
||||
@@ -701,24 +865,49 @@ onMounted(async () => {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-top: 24px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid var(--border-light);
|
||||
}
|
||||
|
||||
.action-buttons .ant-btn[type="primary"] {
|
||||
height: 48px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.generate-hint {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
padding: 8px 12px;
|
||||
background: rgba(255, 193, 7, 0.1);
|
||||
border: 1px solid rgba(255, 193, 7, 0.3);
|
||||
padding: 12px 16px;
|
||||
background: rgba(var(--color-warning), 0.1);
|
||||
border: 1px solid rgba(var(--color-warning), 0.3);
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
color: @warning-color;
|
||||
color: var(--color-warning);
|
||||
}
|
||||
|
||||
/* ========== 响应式 ========== */
|
||||
@media (max-width: 1024px) {
|
||||
.kling-content {
|
||||
grid-template-columns: 1fr;
|
||||
padding: 16px;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.upload-panel {
|
||||
position: static;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.video-selection-cards {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.upload-zone {
|
||||
min-height: 240px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -59,12 +59,33 @@
|
||||
|
||||
<!-- 右侧内容区域 -->
|
||||
<div class="material-content">
|
||||
<!-- 头部操作栏 -->
|
||||
<div class="material-content__header">
|
||||
<h2 class="material-content__title">
|
||||
{{ getContentTitle() }}
|
||||
</h2>
|
||||
<!-- 搜索栏 -->
|
||||
<div class="material-content__search">
|
||||
<a-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索文件名"
|
||||
style="width: 300px"
|
||||
allow-clear
|
||||
@press-enter="handleSearch"
|
||||
>
|
||||
<template #prefix>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
</a-input>
|
||||
<a-button type="primary" @click="handleSearch">搜索</a-button>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="material-content__actions">
|
||||
<!-- 全选/取消全选 -->
|
||||
<a-checkbox
|
||||
:checked="selectedFileIds.length === fileList.length && fileList.length > 0"
|
||||
:indeterminate="selectedFileIds.length > 0 && selectedFileIds.length < fileList.length"
|
||||
@change="handleSelectAll"
|
||||
>
|
||||
<span v-if="selectedFileIds.length === 0">全选</span>
|
||||
<span v-else>已选 {{ selectedFileIds.length }}</span>
|
||||
</a-checkbox>
|
||||
|
||||
<a-button type="primary" @click="handleOpenUploadModal">
|
||||
<template #icon>
|
||||
<UploadOutlined />
|
||||
@@ -89,22 +110,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<div class="material-content__search">
|
||||
<a-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索文件名"
|
||||
style="width: 300px"
|
||||
allow-clear
|
||||
@press-enter="handleSearch"
|
||||
>
|
||||
<template #prefix>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
</a-input>
|
||||
<a-button type="primary" @click="handleSearch">搜索</a-button>
|
||||
</div>
|
||||
|
||||
<!-- 文件列表 -->
|
||||
<div class="material-content__list">
|
||||
<a-spin :spinning="loading" tip="加载中..." style="width: 100%; min-height: 400px;">
|
||||
@@ -113,6 +118,7 @@
|
||||
<div
|
||||
v-for="file in fileList"
|
||||
:key="file.id"
|
||||
:data-file-id="file.id"
|
||||
class="material-item"
|
||||
:class="{ 'material-item--selected': selectedFileIds.includes(file.id) }"
|
||||
@click="handleFileClick(file)"
|
||||
@@ -125,6 +131,7 @@
|
||||
:src="file.coverBase64"
|
||||
:alt="file.fileName"
|
||||
@error="handleImageError"
|
||||
loading="lazy"
|
||||
/>
|
||||
<div v-else class="material-item__preview-placeholder">
|
||||
<FileOutlined />
|
||||
@@ -133,10 +140,27 @@
|
||||
|
||||
<!-- 文件信息 -->
|
||||
<div class="material-item__info">
|
||||
<div class="material-item__name" :title="file.fileName">
|
||||
{{ file.fileName }}
|
||||
<div
|
||||
class="material-item__name"
|
||||
:title="file.displayName"
|
||||
@click="handleEditName(file)"
|
||||
>
|
||||
<template v-if="editingFileId !== file.id">
|
||||
{{ file.displayName }}
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-input
|
||||
v-model:value="editingDisplayName"
|
||||
size="small"
|
||||
@blur="handleSaveName(file)"
|
||||
@press-enter="handleSaveName(file)"
|
||||
@click.stop
|
||||
autofocus
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
<div class="material-item__meta">
|
||||
<span class="material-item__type">{{ getFileTypeText(file.fileName) }}</span>
|
||||
<span class="material-item__size">{{ formatFileSize(file.fileSize) }}</span>
|
||||
<span class="material-item__time">{{ formatDate(file.createTime) }}</span>
|
||||
</div>
|
||||
@@ -154,7 +178,13 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<a-empty v-else description="暂无素材" />
|
||||
<a-empty
|
||||
v-else
|
||||
description="暂无素材"
|
||||
style="padding: 48px 0;"
|
||||
>
|
||||
|
||||
</a-empty>
|
||||
</a-spin>
|
||||
</div>
|
||||
|
||||
@@ -191,9 +221,6 @@
|
||||
<a-form-item label="分组名称" name="name" :rules="[{ required: true, message: '请输入分组名称' }]">
|
||||
<a-input v-model:value="createGroupForm.name" placeholder="请输入分组名称" />
|
||||
</a-form-item>
|
||||
<a-form-item label="分组分类">
|
||||
<a-input v-model:value="createGroupForm.category" disabled />
|
||||
</a-form-item>
|
||||
<a-form-item label="分组描述">
|
||||
<a-textarea v-model:value="createGroupForm.description" placeholder="请输入分组描述(可选)" />
|
||||
</a-form-item>
|
||||
@@ -211,7 +238,7 @@ import {
|
||||
FolderOutlined,
|
||||
PlusOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import MaterialUploadModal from '@/components/material/MaterialUploadModal.vue';
|
||||
import MaterialService, { MaterialGroupService } from '@/api/material';
|
||||
import { useUpload } from '@/composables/useUpload';
|
||||
@@ -240,6 +267,10 @@ const createGroupForm = reactive({
|
||||
// 选中的文件ID列表
|
||||
const selectedFileIds = ref([]);
|
||||
|
||||
// 编辑状态
|
||||
const editingFileId = ref(null);
|
||||
const editingDisplayName = ref('');
|
||||
|
||||
// 分页信息
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
@@ -258,6 +289,10 @@ const handleCategoryChange = async (category) => {
|
||||
|
||||
|
||||
const handleSelectGroup = (group) => {
|
||||
// 如果点击的是当前已选中的分组,则不执行任何操作
|
||||
if (selectedGroupId.value === group.id) {
|
||||
return;
|
||||
}
|
||||
selectedGroupId.value = group.id;
|
||||
loadFileList();
|
||||
};
|
||||
@@ -351,26 +386,20 @@ const handlePageSizeChange = (current, size) => {
|
||||
};
|
||||
|
||||
const handleFileClick = (file) => {
|
||||
// 切换文件选中状态
|
||||
const isSelected = selectedFileIds.value.includes(file.id);
|
||||
if (isSelected) {
|
||||
// 如果已选中,则取消选中
|
||||
selectedFileIds.value = selectedFileIds.value.filter(id => id !== file.id);
|
||||
const index = selectedFileIds.value.indexOf(file.id);
|
||||
if (index > -1) {
|
||||
selectedFileIds.value.splice(index, 1);
|
||||
} else {
|
||||
// 如果未选中,则添加到选中列表
|
||||
if (!selectedFileIds.value.includes(file.id)) {
|
||||
selectedFileIds.value.push(file.id);
|
||||
}
|
||||
selectedFileIds.value.push(file.id);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileSelectChange = (fileId, checked) => {
|
||||
if (checked) {
|
||||
if (!selectedFileIds.value.includes(fileId)) {
|
||||
selectedFileIds.value.push(fileId);
|
||||
}
|
||||
} else {
|
||||
selectedFileIds.value = selectedFileIds.value.filter(id => id !== fileId);
|
||||
const index = selectedFileIds.value.indexOf(fileId);
|
||||
if (checked && index === -1) {
|
||||
selectedFileIds.value.push(fileId);
|
||||
} else if (!checked && index > -1) {
|
||||
selectedFileIds.value.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -408,17 +437,74 @@ const handleFileUpload = async (filesWithCover, category, groupId) => {
|
||||
};
|
||||
|
||||
|
||||
const handleBatchDelete = async () => {
|
||||
// TODO: 实现批量删除
|
||||
message.info('批量删除功能待实现');
|
||||
const handleSelectAll = (event) => {
|
||||
const checked = event.target.checked;
|
||||
if (checked) {
|
||||
// 全选:选中当前页所有文件
|
||||
selectedFileIds.value = fileList.value.map(file => file.id);
|
||||
} else {
|
||||
// 取消全选:清空选中状态
|
||||
selectedFileIds.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
const getContentTitle = () => {
|
||||
if (selectedGroupId.value === null) {
|
||||
return activeCategory.value === 'MIX' ? '混剪素材' : '数字人素材';
|
||||
const handleBatchDelete = async () => {
|
||||
if (selectedFileIds.value.length === 0) {
|
||||
message.warning('请先选择要删除的文件');
|
||||
return;
|
||||
}
|
||||
|
||||
// 确认对话框
|
||||
const count = selectedFileIds.value.length;
|
||||
const fileIdsToDelete = [...selectedFileIds.value]; // 保存要删除的文件ID
|
||||
const confirmed = await new Promise((resolve) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: `确定要删除选中的 ${count} 个文件吗?此操作不可恢复。`,
|
||||
okText: '确认删除',
|
||||
cancelText: '取消',
|
||||
okType: 'danger',
|
||||
centered: true,
|
||||
class: 'batch-delete-modal',
|
||||
onOk() {
|
||||
resolve(true);
|
||||
},
|
||||
onCancel() {
|
||||
resolve(false);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
await MaterialService.deleteFiles(fileIdsToDelete);
|
||||
|
||||
// 从列表中移除已删除的文件
|
||||
fileList.value = fileList.value.filter(file => !fileIdsToDelete.includes(file.id));
|
||||
|
||||
// 更新总数
|
||||
totalFileCount.value = Math.max(0, totalFileCount.value - count);
|
||||
|
||||
// 清空选中状态
|
||||
selectedFileIds.value = [];
|
||||
|
||||
// 如果删除后当前页没有数据了,且不是第一页,则加载上一页
|
||||
if (fileList.value.length === 0 && pagination.current > 1) {
|
||||
pagination.current = pagination.current - 1;
|
||||
await loadFileList();
|
||||
}
|
||||
|
||||
message.success(`成功删除 ${count} 个文件`);
|
||||
} catch (error) {
|
||||
console.error('批量删除失败:', error);
|
||||
message.error('删除失败,请重试');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
const group = groupList.value.find(g => g.id === selectedGroupId.value);
|
||||
return group ? group.name : '素材列表';
|
||||
};
|
||||
|
||||
const formatFileSize = (size) => {
|
||||
@@ -432,6 +518,60 @@ const formatDate = (date) => {
|
||||
return new Date(date).toLocaleDateString();
|
||||
};
|
||||
|
||||
const getFileTypeText = (fileName) => {
|
||||
if (!fileName) return '';
|
||||
// 提取文件后缀,如 .mp3、.mp4、.avi 等
|
||||
const ext = fileName.split('.').pop();
|
||||
return ext ? `${ext.toLowerCase()}` : '';
|
||||
};
|
||||
|
||||
const handleEditName = (file) => {
|
||||
editingFileId.value = file.id;
|
||||
editingDisplayName.value = file.displayName || file.fileName;
|
||||
// 延迟聚焦,确保输入框已渲染
|
||||
setTimeout(() => {
|
||||
const input = document.querySelector('.ant-input:focus');
|
||||
if (input) {
|
||||
input.focus();
|
||||
input.select();
|
||||
}
|
||||
}, 50);
|
||||
};
|
||||
|
||||
const handleSaveName = async (file) => {
|
||||
if (!editingDisplayName.value.trim()) {
|
||||
message.warning('名称不能为空');
|
||||
editingFileId.value = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (editingDisplayName.value === file.displayName) {
|
||||
editingFileId.value = null;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await MaterialService.updateDisplayName(file.id, editingDisplayName.value.trim());
|
||||
file.displayName = editingDisplayName.value.trim();
|
||||
message.success('重命名成功');
|
||||
|
||||
// 添加成功动画效果
|
||||
const nameElement = document.querySelector(`[data-file-id="${file.id}"] .material-item__name`);
|
||||
if (nameElement) {
|
||||
nameElement.style.transition = 'all 0.3s ease';
|
||||
nameElement.style.transform = 'scale(1.05)';
|
||||
setTimeout(() => {
|
||||
nameElement.style.transform = 'scale(1)';
|
||||
}, 300);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('重命名失败:', error);
|
||||
message.error('重命名失败,请重试');
|
||||
} finally {
|
||||
editingFileId.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
const handleImageError = (e) => {
|
||||
e.target.style.display = 'none';
|
||||
};
|
||||
@@ -568,6 +708,11 @@ onMounted(() => {
|
||||
&--active {
|
||||
background: #e6f7ff;
|
||||
color: #1890ff;
|
||||
|
||||
// 移除active状态下的hover效果
|
||||
&:hover {
|
||||
background: #e6f7ff;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
@@ -606,129 +751,349 @@ onMounted(() => {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #fff;
|
||||
margin: 16px;
|
||||
border-radius: 8px;
|
||||
background: #F8FAFC;
|
||||
margin: 0px 16px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
|
||||
&__header {
|
||||
&__search {
|
||||
padding: 20px 32px;
|
||||
border-bottom: 1px solid #E2E8F0;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 24px;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
&__title {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
gap: 16px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
&__actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-left: auto;
|
||||
|
||||
&__search {
|
||||
padding: 16px 24px;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
.ant-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background: #F1F5F9;
|
||||
}
|
||||
|
||||
.ant-checkbox__inner {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.ant-checkbox-checked {
|
||||
.ant-checkbox__inner {
|
||||
background-color: #3B82F6;
|
||||
border-color: #3B82F6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-btn {
|
||||
transition: all 0.2s ease;
|
||||
font-weight: 500;
|
||||
border-radius: 6px;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 8px rgba(59, 130, 246, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__list {
|
||||
flex: 1;
|
||||
padding: 24px;
|
||||
padding: 32px;
|
||||
overflow-y: auto;
|
||||
background: #F8FAFC;
|
||||
}
|
||||
|
||||
&__pagination {
|
||||
padding: 16px 24px;
|
||||
border-top: 1px solid #e8e8e8;
|
||||
padding: 20px 32px;
|
||||
border-top: 1px solid #E2E8F0;
|
||||
background: #fff;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.material-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 16px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 24px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.material-item {
|
||||
position: relative;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
border: 1px solid #E2E8F0;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
|
||||
&:hover {
|
||||
border-color: #1890ff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
border-color: #3B82F6;
|
||||
box-shadow: 0 8px 24px rgba(59, 130, 246, 0.15);
|
||||
}
|
||||
|
||||
&:active {
|
||||
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.2);
|
||||
}
|
||||
|
||||
&--selected {
|
||||
border-color: #1890ff;
|
||||
background: #e6f7ff;
|
||||
border-color: #3B82F6;
|
||||
background: linear-gradient(to bottom, #EFF6FF, #fff);
|
||||
box-shadow: 0 8px 24px rgba(59, 130, 246, 0.2);
|
||||
|
||||
.material-item__select {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
padding: 12px;
|
||||
padding: 16px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
&__preview {
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
height: 160px;
|
||||
background: linear-gradient(135deg, #F1F5F9 0%, #E2E8F0 100%);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 12px;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
&:hover img {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(59, 130, 246, 0);
|
||||
transition: all 0.3s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:hover::after {
|
||||
background: rgba(59, 130, 246, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
&__preview-placeholder {
|
||||
font-size: 48px;
|
||||
color: #d9d9d9;
|
||||
color: #94A3B8;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
color: #3B82F6;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
&__info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
&__name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #1E293B;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
line-height: 1.4;
|
||||
transition: color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
color: #3B82F6;
|
||||
}
|
||||
|
||||
.ant-input {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
height: 32px;
|
||||
line-height: 30px;
|
||||
border-radius: 6px;
|
||||
border-color: #3B82F6;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.15);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
color: #64748B;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid #F1F5F9;
|
||||
|
||||
.material-item__type {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-weight: 500;
|
||||
color: #3B82F6;
|
||||
background: #EFF6FF;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.material-item__size {
|
||||
flex-shrink: 0;
|
||||
margin-left: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.material-item__time {
|
||||
flex-shrink: 0;
|
||||
margin-left: 12px;
|
||||
color: #94A3B8;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
&__select {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 4px;
|
||||
padding: 4px;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 8px;
|
||||
padding: 6px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
transition: all 0.2s ease;
|
||||
backdrop-filter: blur(8px);
|
||||
|
||||
.ant-checkbox {
|
||||
&:hover .ant-checkbox__inner {
|
||||
border-color: #3B82F6;
|
||||
}
|
||||
|
||||
&-checked .ant-checkbox__inner {
|
||||
background-color: #3B82F6;
|
||||
border-color: #3B82F6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 键盘导航支持
|
||||
.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) {
|
||||
.ant-modal-body {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.ant-modal-footer {
|
||||
text-align: right;
|
||||
padding: 16px 24px;
|
||||
border-top: 1px solid #e8e8e8;
|
||||
|
||||
.ant-btn {
|
||||
margin-left: 8px;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user