更新新需求-采集
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<div class="ai-asset-dropzone">
|
||||
<div class="ai-asset-dropzone__head">
|
||||
<div>
|
||||
<div class="ai-asset-dropzone__title">{{ title }}</div>
|
||||
<p v-if="description" class="ai-asset-dropzone__description">{{ description }}</p>
|
||||
</div>
|
||||
<el-button
|
||||
v-if="fileName"
|
||||
text
|
||||
type="primary"
|
||||
class="ai-asset-dropzone__clear"
|
||||
@click="$emit('clear')"
|
||||
>
|
||||
清空
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-upload
|
||||
drag
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
:accept="accept"
|
||||
class="ai-asset-dropzone__upload"
|
||||
@change="handleChange"
|
||||
>
|
||||
<div v-if="previewUrl || (fileName && previewKind === 'file')" class="ai-asset-dropzone__preview">
|
||||
<img
|
||||
v-if="previewKind === 'image'"
|
||||
:src="previewUrl"
|
||||
class="ai-asset-dropzone__media"
|
||||
alt=""
|
||||
/>
|
||||
<video
|
||||
v-else-if="previewKind === 'video'"
|
||||
:src="previewUrl"
|
||||
class="ai-asset-dropzone__media"
|
||||
controls
|
||||
/>
|
||||
<div v-else class="ai-asset-dropzone__file">
|
||||
<span>{{ fileName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="ai-asset-dropzone__empty">
|
||||
<div class="ai-asset-dropzone__placeholder">{{ placeholder }}</div>
|
||||
<div v-if="helper" class="ai-asset-dropzone__helper">{{ helper }}</div>
|
||||
</div>
|
||||
</el-upload>
|
||||
|
||||
<div v-if="fileName" class="ai-asset-dropzone__footer">
|
||||
<span class="ai-asset-dropzone__filename">{{ fileName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { UploadFile, UploadFiles } from 'element-plus'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
title: string
|
||||
description?: string
|
||||
placeholder?: string
|
||||
helper?: string
|
||||
accept?: string
|
||||
fileName?: string
|
||||
previewUrl?: string
|
||||
previewKind?: 'image' | 'video' | 'file'
|
||||
}>(),
|
||||
{
|
||||
description: '',
|
||||
placeholder: '点击或拖拽上传素材',
|
||||
helper: '',
|
||||
accept: '',
|
||||
fileName: '',
|
||||
previewUrl: '',
|
||||
previewKind: 'file',
|
||||
},
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'select', file: File): void
|
||||
(event: 'clear'): void
|
||||
}>()
|
||||
|
||||
function handleChange(uploadFile: UploadFile, _files: UploadFiles) {
|
||||
const rawFile = uploadFile.raw
|
||||
if (!rawFile) {
|
||||
return
|
||||
}
|
||||
emit('select', rawFile)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ai-asset-dropzone {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.ai-asset-dropzone__head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.ai-asset-dropzone__title {
|
||||
color: #f2f2f2;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ai-asset-dropzone__description {
|
||||
margin: 6px 0 0;
|
||||
color: #9d9d9d;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
:deep(.ai-asset-dropzone__upload .el-upload) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.ai-asset-dropzone__upload .el-upload-dragger) {
|
||||
width: 100%;
|
||||
min-height: 176px;
|
||||
padding: 12px;
|
||||
border: 1px dashed #474747;
|
||||
border-radius: 14px;
|
||||
background: #1f1f1f;
|
||||
transition: border-color 0.2s ease, background 0.2s ease;
|
||||
}
|
||||
|
||||
:deep(.ai-asset-dropzone__upload .el-upload-dragger:hover) {
|
||||
border-color: #8c63ff;
|
||||
background: #232323;
|
||||
}
|
||||
|
||||
.ai-asset-dropzone__preview,
|
||||
.ai-asset-dropzone__empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 150px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ai-asset-dropzone__empty {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.ai-asset-dropzone__placeholder {
|
||||
color: #f0f0f0;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.ai-asset-dropzone__helper {
|
||||
color: #8d8d8d;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.ai-asset-dropzone__media {
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
object-fit: cover;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.ai-asset-dropzone__file {
|
||||
color: #d8d8d8;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.ai-asset-dropzone__footer {
|
||||
padding: 10px 12px;
|
||||
border-radius: 10px;
|
||||
background: #1f1f1f;
|
||||
border: 1px solid #383838;
|
||||
}
|
||||
|
||||
.ai-asset-dropzone__filename {
|
||||
color: #d8d8d8;
|
||||
font-size: 12px;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<div class="ai-choice-pills" :class="{ 'ai-choice-pills--wrap': wrap }">
|
||||
<button
|
||||
v-for="option in normalizedOptions"
|
||||
:key="option.value"
|
||||
type="button"
|
||||
class="ai-choice-pills__item"
|
||||
:class="{ 'ai-choice-pills__item--active': isActive(option.value) }"
|
||||
@click="toggle(option.value)"
|
||||
>
|
||||
{{ option.label }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
type Option = string | { label: string; value: string }
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue: string | string[]
|
||||
options: Option[]
|
||||
multiple?: boolean
|
||||
wrap?: boolean
|
||||
}>(),
|
||||
{
|
||||
multiple: false,
|
||||
wrap: true,
|
||||
},
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', value: string | string[]): void
|
||||
}>()
|
||||
|
||||
const normalizedOptions = computed(() =>
|
||||
props.options.map((option) =>
|
||||
typeof option === 'string' ? { label: option, value: option } : option,
|
||||
),
|
||||
)
|
||||
|
||||
function isActive(value: string) {
|
||||
return props.multiple
|
||||
? Array.isArray(props.modelValue) && props.modelValue.includes(value)
|
||||
: props.modelValue === value
|
||||
}
|
||||
|
||||
function toggle(value: string) {
|
||||
if (!props.multiple) {
|
||||
emit('update:modelValue', value)
|
||||
return
|
||||
}
|
||||
|
||||
const current = Array.isArray(props.modelValue) ? [...props.modelValue] : []
|
||||
const index = current.indexOf(value)
|
||||
if (index >= 0) {
|
||||
current.splice(index, 1)
|
||||
} else {
|
||||
current.push(value)
|
||||
}
|
||||
emit('update:modelValue', current)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ai-choice-pills {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.ai-choice-pills--wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.ai-choice-pills__item {
|
||||
min-height: 40px;
|
||||
padding: 0 18px;
|
||||
border: 1px solid #4b4b4b;
|
||||
border-radius: 999px;
|
||||
background: #1b1b1b;
|
||||
color: #dddddd;
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.18s ease, color 0.18s ease, background 0.18s ease;
|
||||
}
|
||||
|
||||
.ai-choice-pills__item:hover {
|
||||
border-color: #8a8a8a;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.ai-choice-pills__item--active {
|
||||
border-color: #8c63ff;
|
||||
background: rgba(140, 99, 255, 0.16);
|
||||
color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<div class="ai-module-switch" role="tablist" aria-label="功能模块切换">
|
||||
<button
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
type="button"
|
||||
class="ai-module-switch__item"
|
||||
:class="{
|
||||
'ai-module-switch__item--active': modelValue === item.value,
|
||||
'ai-module-switch__item--disabled': item.disabled,
|
||||
}"
|
||||
:disabled="item.disabled"
|
||||
@click="$emit('update:modelValue', item.value)"
|
||||
>
|
||||
<span class="ai-module-switch__label">{{ item.label }}</span>
|
||||
<span v-if="item.description" class="ai-module-switch__description">
|
||||
{{ item.description }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
export type AiModuleSwitchOption = {
|
||||
label: string
|
||||
value: string
|
||||
description?: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
modelValue: string
|
||||
options: AiModuleSwitchOption[]
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
(event: 'update:modelValue', value: string): void
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ai-module-switch {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.ai-module-switch__item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
min-height: 84px;
|
||||
padding: 16px 18px;
|
||||
border: 1px solid rgba(71, 85, 105, 0.65);
|
||||
border-radius: 16px;
|
||||
background: rgba(15, 23, 42, 0.72);
|
||||
color: #cbd5e1;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
border-color 0.2s ease,
|
||||
transform 0.2s ease,
|
||||
box-shadow 0.2s ease,
|
||||
background 0.2s ease;
|
||||
}
|
||||
|
||||
.ai-module-switch__item:hover:not(:disabled) {
|
||||
border-color: rgba(96, 165, 250, 0.7);
|
||||
box-shadow: 0 14px 24px rgba(8, 15, 31, 0.34);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.ai-module-switch__item--active {
|
||||
border-color: rgba(96, 165, 250, 0.82);
|
||||
background:
|
||||
linear-gradient(180deg, rgba(30, 41, 59, 0.96), rgba(15, 23, 42, 0.96));
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(59, 130, 246, 0.18),
|
||||
0 18px 28px rgba(8, 15, 31, 0.36);
|
||||
}
|
||||
|
||||
.ai-module-switch__item--disabled {
|
||||
opacity: 0.62;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.ai-module-switch__label {
|
||||
color: #f8fafc;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ai-module-switch__description {
|
||||
color: #94a3b8;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.ai-module-switch {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<section class="ai-section-card">
|
||||
<header class="ai-section-card__header">
|
||||
<div class="ai-section-card__title-wrap">
|
||||
<span v-if="step" class="ai-section-card__badge">{{ step }}</span>
|
||||
<div class="ai-section-card__title-group">
|
||||
<h3 class="ai-section-card__title">{{ title }}</h3>
|
||||
<p v-if="description" class="ai-section-card__description">{{ description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="$slots.extra" class="ai-section-card__extra">
|
||||
<slot name="extra" />
|
||||
</div>
|
||||
</header>
|
||||
<div class="ai-section-card__body">
|
||||
<slot />
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
title: string
|
||||
step?: string
|
||||
description?: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ai-section-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
border: 1px solid #343434;
|
||||
border-radius: 18px;
|
||||
background: #171717;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.02);
|
||||
}
|
||||
|
||||
.ai-section-card__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 14px 18px;
|
||||
border-bottom: 1px solid #303030;
|
||||
}
|
||||
|
||||
.ai-section-card__title-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ai-section-card__badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(135deg, #6a5cff 0%, #4f7bff 100%);
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.ai-section-card__title-group {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.ai-section-card__title {
|
||||
margin: 0;
|
||||
color: #fff;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.ai-section-card__description {
|
||||
margin: 6px 0 0;
|
||||
color: #9d9d9d;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.ai-section-card__extra {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ai-section-card__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding: 16px 18px 18px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<div class="ai-workflow-shell">
|
||||
<div class="ai-workflow-shell__grid" aria-hidden="true"></div>
|
||||
<div class="ai-workflow-shell__inner">
|
||||
<header class="ai-workflow-shell__header">
|
||||
<div class="ai-workflow-shell__meta">
|
||||
<slot name="meta" />
|
||||
</div>
|
||||
<div class="ai-workflow-shell__actions">
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
</header>
|
||||
<div class="ai-workflow-shell__toolbar">
|
||||
<slot name="toolbar" />
|
||||
</div>
|
||||
<main class="ai-workflow-shell__content">
|
||||
<slot />
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.ai-workflow-shell {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
overflow: hidden;
|
||||
background:
|
||||
radial-gradient(circle at top center, rgba(59, 130, 246, 0.14), transparent 32%),
|
||||
radial-gradient(circle at right bottom, rgba(99, 102, 241, 0.12), transparent 24%),
|
||||
linear-gradient(180deg, #060913 0%, #09111d 40%, #070b14 100%);
|
||||
}
|
||||
|
||||
.ai-workflow-shell__grid {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(77, 104, 145, 0.08) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(77, 104, 145, 0.08) 1px, transparent 1px);
|
||||
background-size: 40px 40px;
|
||||
mask-image: radial-gradient(circle at 50% 18%, rgba(0, 0, 0, 0.9), transparent 75%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.ai-workflow-shell__inner {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
min-height: 100vh;
|
||||
padding: 28px;
|
||||
}
|
||||
|
||||
.ai-workflow-shell__header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.ai-workflow-shell__meta,
|
||||
.ai-workflow-shell__actions {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ai-workflow-shell__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.ai-workflow-shell__toolbar {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.ai-workflow-shell__content {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.ai-workflow-shell__inner {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.ai-workflow-shell__header {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.ai-workflow-shell__actions {
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user