优化筛选组件UI:深灰色背景、绿色选中文字、添加label属性

This commit is contained in:
Your Name 2026-01-17 11:33:27 +08:00
parent 8789143501
commit 218f01d6b1
3 changed files with 808 additions and 748 deletions

View File

@ -0,0 +1,600 @@
<template>
<div class="search-filter-bar chart-card">
<!-- 第一行:搜索、排序和统计 -->
<div class="search-row">
<a-input-search
v-model:value="localFilters.keyword"
:placeholder="searchPlaceholder"
class="search-input"
@search="handleFilterChange"
@pressEnter="handleFilterChange"
/>
<a-select
v-model:value="localFilters.sortBy"
placeholder="排序方式"
class="sort-select"
@change="handleFilterChange"
>
<a-select-option
v-for="option in sortOptions"
:key="option.value"
:value="option.value"
:label="option.label"
>
{{ option.label }}
</a-select-option>
</a-select>
<span class="stats-text">{{ statsText }}</span>
</div>
<!-- 第二行:筛选条件 -->
<div class="filter-row">
<!-- 状态筛选 -->
<a-select
v-if="showStatus"
v-model:value="localFilters.status"
:placeholder="statusPlaceholder"
class="filter-select"
allowClear
@change="handleFilterChange"
>
<a-select-option
v-for="option in statusOptions"
:key="option.value"
:value="option.value"
:label="option.label"
>
{{ option.label }}
</a-select-option>
</a-select>
<!-- 任务类型筛选 -->
<a-select
v-if="showTaskType"
v-model:value="localFilters.taskTypeId"
placeholder="任务类型"
class="filter-select"
allowClear
@change="handleFilterChange"
>
<a-select-option
v-for="type in demandTypes"
:key="type.id"
:value="type.id"
:label="type.name"
>
{{ type.name }}
</a-select-option>
</a-select>
<!-- 需求类型筛选(新品/优化/视频/实拍) -->
<a-select
v-if="showTaskSubType"
v-model:value="localFilters.taskSubType"
placeholder="需求类型"
class="filter-select"
allowClear
@change="handleFilterChange"
>
<a-select-option
v-for="subType in taskSubTypes"
:key="subType.id"
:value="subType.name"
:label="subType.name"
>
{{ subType.name }}
</a-select-option>
</a-select>
<!-- 品牌筛选 -->
<a-select
v-if="showBrand"
v-model:value="localFilters.brandId"
placeholder="品牌"
class="filter-select"
allowClear
@change="handleFilterChange"
>
<a-select-option
v-for="brand in brands"
:key="brand.id"
:value="brand.id"
:label="brand.name"
>
{{ brand.name }}
</a-select-option>
</a-select>
<!-- 类目筛选 -->
<a-select
v-if="showCategory"
v-model:value="localFilters.categoryId"
placeholder="类目"
class="filter-select"
allowClear
@change="handleFilterChange"
>
<a-select-option
v-for="category in categories"
:key="category.id"
:value="category.id"
:label="category.name"
>
{{ category.name }}
</a-select-option>
</a-select>
<!-- 平台筛选 -->
<a-select
v-if="showPlatform"
v-model:value="localFilters.platformId"
placeholder="平台"
class="filter-select"
allowClear
@change="handleFilterChange"
>
<a-select-option
v-for="platform in platforms"
:key="platform.id"
:value="platform.id"
:label="platform.name"
>
{{ platform.name }}
</a-select-option>
</a-select>
<!-- 接单部门筛选 -->
<a-select
v-if="showReceivingDept"
v-model:value="localFilters.filterDeptId"
placeholder="接单部门"
class="filter-select"
allowClear
@change="handleFilterChange"
>
<a-select-option
v-for="dept in receivingDeptList"
:key="dept.id"
:value="dept.id"
:label="dept.deptName"
>
{{ dept.deptName }}
</a-select-option>
</a-select>
<!-- 时间范围筛选 -->
<a-select
v-if="showTimeRange"
v-model:value="localFilters.timeRange"
:placeholder="timeRangePlaceholder"
class="filter-select"
allowClear
@change="handleFilterChange"
>
<a-select-option value="today" label="今天">今天</a-select-option>
<a-select-option value="week" label="本周">本周</a-select-option>
<a-select-option value="month" label="本月">本月</a-select-option>
</a-select>
<!-- 积分范围筛选 -->
<div v-if="showScoreRange" class="score-range">
<a-input
v-model:value="localFilters.minScore"
placeholder="最小积分"
class="score-input"
@pressEnter="handleFilterChange"
/>
<span class="score-separator">-</span>
<a-input
v-model:value="localFilters.maxScore"
placeholder="最大积分"
class="score-input"
@pressEnter="handleFilterChange"
/>
</div>
<!-- 进度范围筛选(用于进行中任务) -->
<div v-if="showProgressRange" class="score-range">
<a-input-number
v-model:value="localFilters.minProgress"
placeholder="最小进度%"
class="score-input"
:min="0"
:max="100"
@change="handleFilterChange"
/>
<span class="score-separator">-</span>
<a-input-number
v-model:value="localFilters.maxProgress"
placeholder="最大进度%"
class="score-input"
:min="0"
:max="100"
@change="handleFilterChange"
/>
</div>
<a-button @click="handleReset" class="reset-btn">重置</a-button>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive, watch, PropType } from 'vue'
// 筛选项类型定义
interface FilterOption {
value: string
label: string
}
interface DemandType {
id: number
name: string
}
interface TaskSubType {
id: number
name: string
}
interface Brand {
id: number
name: string
}
interface Category {
id: number
name: string
}
interface Platform {
id: number
name: string
}
interface Department {
id: number
deptName: string
}
interface FilterValues {
keyword?: string
status?: string
sortBy?: string
taskTypeId?: number
taskSubType?: string
brandId?: number
categoryId?: number
platformId?: number
filterDeptId?: number
timeRange?: string
minScore?: number
maxScore?: number
minProgress?: number
maxProgress?: number
}
// Props 定义
const props = defineProps({
// 筛选条件值(双向绑定)
modelValue: {
type: Object as PropType<FilterValues>,
default: () => ({})
},
// 搜索框占位符
searchPlaceholder: {
type: String,
default: '搜索任务编号、标题、SPU信息、发布人...'
},
// 统计文本
statsText: {
type: String,
default: '共 0 个任务'
},
// 排序选项
sortOptions: {
type: Array as PropType<FilterOption[]>,
default: () => [
{ value: 'latest', label: '最新发布' },
{ value: 'score', label: '积分最高' },
{ value: 'deadline', label: '时效最短' }
]
},
// 状态筛选配置
showStatus: {
type: Boolean,
default: true
},
statusPlaceholder: {
type: String,
default: '任务状态'
},
statusOptions: {
type: Array as PropType<FilterOption[]>,
default: () => [
{ value: '', label: '全部状态' }
]
},
// 显示控制
showTaskType: {
type: Boolean,
default: true
},
showTaskSubType: {
type: Boolean,
default: true
},
showBrand: {
type: Boolean,
default: true
},
showCategory: {
type: Boolean,
default: true
},
showPlatform: {
type: Boolean,
default: true
},
showReceivingDept: {
type: Boolean,
default: false
},
showTimeRange: {
type: Boolean,
default: true
},
timeRangePlaceholder: {
type: String,
default: '发布时间'
},
showScoreRange: {
type: Boolean,
default: true
},
showProgressRange: {
type: Boolean,
default: false
},
// 数据源
demandTypes: {
type: Array as PropType<DemandType[]>,
default: () => []
},
taskSubTypes: {
type: Array as PropType<TaskSubType[]>,
default: () => []
},
brands: {
type: Array as PropType<Brand[]>,
default: () => []
},
categories: {
type: Array as PropType<Category[]>,
default: () => []
},
platforms: {
type: Array as PropType<Platform[]>,
default: () => []
},
receivingDeptList: {
type: Array as PropType<Department[]>,
default: () => []
}
})
// Emits 定义
const emit = defineEmits(['update:modelValue', 'change', 'reset'])
// 本地筛选状态
const localFilters = reactive<FilterValues>({
keyword: props.modelValue?.keyword,
status: props.modelValue?.status || '',
sortBy: props.modelValue?.sortBy || 'latest',
taskTypeId: props.modelValue?.taskTypeId,
taskSubType: props.modelValue?.taskSubType,
brandId: props.modelValue?.brandId,
categoryId: props.modelValue?.categoryId,
platformId: props.modelValue?.platformId,
filterDeptId: props.modelValue?.filterDeptId,
timeRange: props.modelValue?.timeRange,
minScore: props.modelValue?.minScore,
maxScore: props.modelValue?.maxScore,
minProgress: props.modelValue?.minProgress,
maxProgress: props.modelValue?.maxProgress
})
// 监听 props.modelValue 变化,同步到本地状态
watch(
() => props.modelValue,
(newVal) => {
if (newVal) {
Object.assign(localFilters, newVal)
}
},
{ deep: true }
)
// 筛选条件变更
const handleFilterChange = () => {
emit('update:modelValue', { ...localFilters })
emit('change', { ...localFilters })
}
// 重置筛选条件
const handleReset = () => {
localFilters.keyword = undefined
localFilters.status = ''
localFilters.sortBy = props.sortOptions[0]?.value || 'latest'
localFilters.taskTypeId = undefined
localFilters.taskSubType = undefined
localFilters.brandId = undefined
localFilters.categoryId = undefined
localFilters.platformId = undefined
localFilters.filterDeptId = undefined
localFilters.timeRange = undefined
localFilters.minScore = undefined
localFilters.maxScore = undefined
localFilters.minProgress = undefined
localFilters.maxProgress = undefined
emit('update:modelValue', { ...localFilters })
emit('reset')
emit('change', { ...localFilters })
}
</script>
<style lang="scss" scoped>
@import '@/styles/variables.scss';
// 复用图表卡片样式,与任务卡片统一
.chart-card {
background: $bg-card;
border: 1px solid $border-primary;
border-radius: $radius-lg;
padding: $space-md; // 筛选栏稍微紧凑一点,用 md 而不是 xl
transition: all $transition-base $ease-out;
&:hover {
border-color: rgba(255, 255, 255, 0.1); // 微调 hover 边框颜色
background: $bg-card-hover;
}
}
.search-filter-bar {
margin-bottom: 16px;
}
.search-row {
display: flex;
align-items: center;
gap: $space-md;
margin-bottom: $space-md;
}
.search-input {
flex: 1;
max-width: 400px;
}
.sort-select {
width: 130px;
}
.stats-text {
color: $text-secondary;
font-size: $font-size-base;
margin-left: auto;
}
.filter-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: $space-sm;
}
.filter-select {
min-width: 110px;
max-width: 140px;
}
.score-range {
display: flex;
align-items: center;
gap: $space-xs;
}
.score-input {
width: 90px;
}
.score-separator {
color: $text-secondary;
}
.reset-btn {
margin-left: auto;
}
// 深色主题适配 - 表单控件统一风格
:deep(.ant-select-selector) {
background-color: #1a1a1a !important; // 深灰色背景
border-color: #333333 !important; // 深色边框
color: $brand-primary !important; // 选中的文字使用绿色,更醒目
border-radius: $radius-base !important; // 统一圆角
&:hover {
border-color: $brand-primary !important;
}
}
// 选中项文字颜色为绿色
:deep(.ant-select-selection-item) {
color: $brand-primary !important;
}
:deep(.ant-select-arrow) {
color: $brand-primary; // 箭头也用绿色
}
:deep(.ant-select-selection-placeholder) {
color: $text-tertiary !important; // placeholder 保持灰色
}
:deep(.ant-input) {
background-color: #1a1a1a; // 深灰色背景
border-color: #333333; // 深色边框
color: $text-primary;
border-radius: $radius-base;
&::placeholder {
color: $text-tertiary;
}
&:hover, &:focus {
border-color: $brand-primary;
}
}
:deep(.ant-input-search-button) {
border-color: $border-primary;
border-radius: 0 $radius-base $radius-base 0 !important; // 修复搜索按钮圆角
}
:deep(.ant-input-number) {
background-color: #1a1a1a; // 深灰色背景
border-color: #333333; // 深色边框
border-radius: $radius-base;
width: 100%;
.ant-input-number-input {
color: $text-primary;
&::placeholder {
color: $text-tertiary;
}
}
&:hover, &:focus {
border-color: $brand-primary;
}
}
:deep(.ant-input-number-focused) {
border-color: $brand-primary;
}
:deep(.ant-btn) {
background-color: #1a1a1a; // 深灰色背景
border-color: #333333; // 深色边框
color: $text-primary;
border-radius: $radius-base;
&:hover {
border-color: $brand-primary;
color: $brand-primary;
}
}
</style>

View File

@ -69,5 +69,6 @@ declare module 'vue' {
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
TaskCard: typeof import('./../components/TaskCard.vue')['default']
TaskFilters: typeof import('./../components/TaskFilters.vue')['default']
}
}

File diff suppressed because it is too large Load Diff