添加 cl-filter-bar 过滤栏组件
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cool-unix",
|
||||
"version": "8.0.14",
|
||||
"version": "8.0.15",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build-ui": "node ./uni_modules/cool-ui/scripts/generate-types.js",
|
||||
|
||||
@@ -310,6 +310,12 @@
|
||||
"navigationBarTitleText": "Draggable 拖拽"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "data/filter-bar",
|
||||
"style": {
|
||||
"navigationBarTitleText": "FilterBar 筛选栏"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "status/badge",
|
||||
"style": {
|
||||
|
||||
465
pages/demo/data/filter-bar.uvue
Normal file
465
pages/demo/data/filter-bar.uvue
Normal file
@@ -0,0 +1,465 @@
|
||||
<template>
|
||||
<cl-page>
|
||||
<view class="p-3">
|
||||
<demo-item :label="t('基础用法')">
|
||||
<cl-filter-bar>
|
||||
<!-- 下拉框 -->
|
||||
<cl-filter-item
|
||||
label="综合排序"
|
||||
type="select"
|
||||
:value="1"
|
||||
:options="coreOptions"
|
||||
:pt="{
|
||||
className: 'w-[220rpx] !flex-none'
|
||||
}"
|
||||
@change="onOptionsChange"
|
||||
></cl-filter-item>
|
||||
|
||||
<!-- 排序 -->
|
||||
<cl-filter-item
|
||||
label="销量"
|
||||
type="sort"
|
||||
value="desc"
|
||||
@change="onSortChange"
|
||||
></cl-filter-item>
|
||||
|
||||
<!-- 开关 -->
|
||||
<cl-filter-item
|
||||
label="国补"
|
||||
type="switch"
|
||||
:value="false"
|
||||
@change="onSwitchChange"
|
||||
></cl-filter-item>
|
||||
|
||||
<!-- 自定义 -->
|
||||
<view
|
||||
class="flex flex-row items-center justify-center flex-1"
|
||||
@tap="openFilter"
|
||||
>
|
||||
<cl-text>筛选</cl-text>
|
||||
<cl-icon name="filter-line"></cl-icon>
|
||||
</view>
|
||||
</cl-filter-bar>
|
||||
</demo-item>
|
||||
|
||||
<demo-item>
|
||||
<cl-text pre-wrap :pt="{ className: '!text-sm p-2' }">{{
|
||||
JSON.stringify(filterForm, null, 4)
|
||||
}}</cl-text>
|
||||
</demo-item>
|
||||
|
||||
<demo-item>
|
||||
<cl-text pre-wrap :pt="{ className: '!text-sm p-2' }">{{
|
||||
JSON.stringify(searchForm, null, 4)
|
||||
}}</cl-text>
|
||||
</demo-item>
|
||||
</view>
|
||||
|
||||
<!-- 自定义筛选 -->
|
||||
<cl-popup
|
||||
v-model="filterVisible"
|
||||
:title="t('筛选')"
|
||||
direction="right"
|
||||
size="80%"
|
||||
:show-header="false"
|
||||
>
|
||||
<view class="flex flex-col h-full">
|
||||
<scroll-view class="flex-1">
|
||||
<cl-form :pt="{ className: 'p-3' }">
|
||||
<cl-form-item label="服务/折扣">
|
||||
<cl-row :gutter="20">
|
||||
<cl-col :span="8" v-for="(item, index) in disOptions" :key="index">
|
||||
<cl-checkbox
|
||||
v-model="searchForm.dis"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
:show-icon="false"
|
||||
:pt="{
|
||||
className: parseClass([
|
||||
'mb-3 p-2 rounded-lg justify-center border border-solid border-transparent',
|
||||
[isDark, 'bg-surface-800', 'bg-surface-100'],
|
||||
[
|
||||
searchForm.dis.includes(item.value),
|
||||
`${isDark ? '!bg-surface-700' : '!bg-white'} !border-primary-500`
|
||||
]
|
||||
]),
|
||||
label: {
|
||||
className: '!text-sm'
|
||||
}
|
||||
}"
|
||||
></cl-checkbox>
|
||||
</cl-col>
|
||||
</cl-row>
|
||||
</cl-form-item>
|
||||
|
||||
<cl-form-item label="价格区间">
|
||||
<view class="flex flex-row items-center">
|
||||
<cl-input
|
||||
v-model="searchForm.minPrice"
|
||||
type="digit"
|
||||
placeholder="最低价"
|
||||
:pt="{
|
||||
className: 'flex-1',
|
||||
inner: {
|
||||
className: 'text-center'
|
||||
}
|
||||
}"
|
||||
></cl-input>
|
||||
<cl-text
|
||||
:pt="{
|
||||
className: 'px-2'
|
||||
}"
|
||||
>~</cl-text
|
||||
>
|
||||
<cl-input
|
||||
v-model="searchForm.maxPrice"
|
||||
type="digit"
|
||||
placeholder="最高价"
|
||||
:pt="{
|
||||
className: 'flex-1',
|
||||
inner: {
|
||||
className: 'text-center'
|
||||
}
|
||||
}"
|
||||
></cl-input>
|
||||
</view>
|
||||
</cl-form-item>
|
||||
|
||||
<cl-form-item label="品牌">
|
||||
<cl-row :gutter="20">
|
||||
<cl-col
|
||||
:span="8"
|
||||
v-for="(item, index) in brandOptions"
|
||||
:key="index"
|
||||
>
|
||||
<cl-checkbox
|
||||
v-model="searchForm.brand"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
:show-icon="false"
|
||||
:pt="{
|
||||
className: parseClass([
|
||||
'mb-3 p-2 rounded-lg justify-center border border-solid border-transparent',
|
||||
[isDark, 'bg-surface-800', 'bg-surface-100'],
|
||||
[
|
||||
searchForm.brand.includes(item.value),
|
||||
`${isDark ? '!bg-surface-700' : '!bg-white'} !border-primary-500`
|
||||
]
|
||||
]),
|
||||
label: {
|
||||
className: '!text-sm'
|
||||
}
|
||||
}"
|
||||
></cl-checkbox>
|
||||
</cl-col>
|
||||
</cl-row>
|
||||
</cl-form-item>
|
||||
|
||||
<cl-form-item label="内存">
|
||||
<cl-row :gutter="20">
|
||||
<cl-col
|
||||
:span="8"
|
||||
v-for="(item, index) in memoryOptions"
|
||||
:key="index"
|
||||
>
|
||||
<cl-radio
|
||||
v-model="searchForm.memory"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
:show-icon="false"
|
||||
:pt="{
|
||||
className: parseClass([
|
||||
'mb-3 p-2 rounded-lg justify-center border border-solid border-transparent',
|
||||
[isDark, 'bg-surface-800', 'bg-surface-100'],
|
||||
[
|
||||
searchForm.memory == item.value,
|
||||
`${isDark ? '!bg-surface-700' : '!bg-white'} !border-primary-500`
|
||||
]
|
||||
]),
|
||||
label: {
|
||||
className: '!text-sm'
|
||||
}
|
||||
}"
|
||||
></cl-radio>
|
||||
</cl-col>
|
||||
</cl-row>
|
||||
</cl-form-item>
|
||||
|
||||
<cl-form-item label="颜色">
|
||||
<cl-row :gutter="20">
|
||||
<cl-col
|
||||
:span="8"
|
||||
v-for="(item, index) in colorOptions"
|
||||
:key="index"
|
||||
>
|
||||
<cl-radio
|
||||
v-model="searchForm.color"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
:show-icon="false"
|
||||
:pt="{
|
||||
className: parseClass([
|
||||
'mb-3 p-2 rounded-lg justify-center border border-solid border-transparent',
|
||||
[isDark, 'bg-surface-800', 'bg-surface-100'],
|
||||
[
|
||||
searchForm.color == item.value,
|
||||
`${isDark ? '!bg-surface-700' : '!bg-white'} !border-primary-500`
|
||||
]
|
||||
]),
|
||||
label: {
|
||||
className: '!text-sm'
|
||||
}
|
||||
}"
|
||||
></cl-radio>
|
||||
</cl-col>
|
||||
</cl-row>
|
||||
</cl-form-item>
|
||||
</cl-form>
|
||||
</scroll-view>
|
||||
|
||||
<view class="flex flex-row p-3">
|
||||
<cl-button
|
||||
type="info"
|
||||
text
|
||||
border
|
||||
:pt="{
|
||||
className: 'flex-1'
|
||||
}"
|
||||
@tap="closeFilter"
|
||||
>{{ t("取消") }}</cl-button
|
||||
>
|
||||
<cl-button
|
||||
:pt="{
|
||||
className: 'flex-1'
|
||||
}"
|
||||
@tap="submit"
|
||||
>{{ t("确定") }}</cl-button
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
</cl-popup>
|
||||
</cl-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { t } from "@/locale";
|
||||
import DemoItem from "../components/item.uvue";
|
||||
import { reactive, ref } from "vue";
|
||||
import { useUi, type ClSelectOption } from "@/uni_modules/cool-ui";
|
||||
import { isDark, parseClass } from "@/cool";
|
||||
|
||||
const ui = useUi();
|
||||
|
||||
const filterVisible = ref(false);
|
||||
|
||||
function openFilter() {
|
||||
filterVisible.value = true;
|
||||
}
|
||||
|
||||
function closeFilter() {
|
||||
filterVisible.value = false;
|
||||
}
|
||||
|
||||
function submit() {
|
||||
closeFilter();
|
||||
|
||||
ui.showLoading();
|
||||
|
||||
setTimeout(() => {
|
||||
ui.hideLoading();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
const coreOptions = ref<ClSelectOption[]>([
|
||||
{
|
||||
label: "综合排序",
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: "价格从高到底",
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
label: "价格从低到高",
|
||||
value: 3
|
||||
}
|
||||
]);
|
||||
|
||||
type Option = {
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
const disOptions = ref<Option[]>([
|
||||
{
|
||||
label: "百亿补贴",
|
||||
value: "billion_subsidy"
|
||||
},
|
||||
{
|
||||
label: "以旧换新",
|
||||
value: "trade_in"
|
||||
},
|
||||
{
|
||||
label: "分期免息",
|
||||
value: "installment"
|
||||
},
|
||||
{
|
||||
label: "包邮",
|
||||
value: "free_shipping"
|
||||
},
|
||||
{
|
||||
label: "促销",
|
||||
value: "promotion"
|
||||
},
|
||||
{
|
||||
label: "价保",
|
||||
value: "price_protection"
|
||||
},
|
||||
{
|
||||
label: "仅看有货",
|
||||
value: "in_stock"
|
||||
},
|
||||
{
|
||||
label: "货到付款",
|
||||
value: "cod"
|
||||
}
|
||||
]);
|
||||
|
||||
const brandOptions = ref<Option[]>([
|
||||
{
|
||||
label: "华为",
|
||||
value: "huawei"
|
||||
},
|
||||
{
|
||||
label: "苹果",
|
||||
value: "apple"
|
||||
},
|
||||
{
|
||||
label: "小米",
|
||||
value: "xiaomi"
|
||||
},
|
||||
{
|
||||
label: "三星",
|
||||
value: "samsung"
|
||||
},
|
||||
{
|
||||
label: "OPPO",
|
||||
value: "oppo"
|
||||
},
|
||||
{
|
||||
label: "vivo",
|
||||
value: "vivo"
|
||||
},
|
||||
{
|
||||
label: "荣耀",
|
||||
value: "honor"
|
||||
}
|
||||
]);
|
||||
|
||||
const colorOptions = ref<Option[]>([
|
||||
{
|
||||
label: "红色",
|
||||
value: "red"
|
||||
},
|
||||
{
|
||||
label: "蓝色",
|
||||
value: "blue"
|
||||
},
|
||||
{
|
||||
label: "黑色",
|
||||
value: "black"
|
||||
},
|
||||
{
|
||||
label: "白色",
|
||||
value: "white"
|
||||
},
|
||||
{
|
||||
label: "金色",
|
||||
value: "gold"
|
||||
},
|
||||
{
|
||||
label: "银色",
|
||||
value: "silver"
|
||||
},
|
||||
{
|
||||
label: "绿色",
|
||||
value: "green"
|
||||
},
|
||||
{
|
||||
label: "紫色",
|
||||
value: "purple"
|
||||
},
|
||||
{
|
||||
label: "灰色",
|
||||
value: "gray"
|
||||
},
|
||||
{
|
||||
label: "粉色",
|
||||
value: "pink"
|
||||
}
|
||||
]);
|
||||
|
||||
const memoryOptions = ref<Option[]>([
|
||||
{
|
||||
label: "128GB",
|
||||
value: "128"
|
||||
},
|
||||
{
|
||||
label: "256GB",
|
||||
value: "256"
|
||||
},
|
||||
{
|
||||
label: "512GB",
|
||||
value: "512"
|
||||
},
|
||||
{
|
||||
label: "1TB",
|
||||
value: "1024"
|
||||
}
|
||||
]);
|
||||
|
||||
type SearchForm = {
|
||||
dis: string[];
|
||||
minPrice: string;
|
||||
maxPrice: string;
|
||||
brand: string[];
|
||||
memory: string;
|
||||
color: string;
|
||||
};
|
||||
|
||||
const searchForm = ref<SearchForm>({
|
||||
dis: [],
|
||||
minPrice: "50",
|
||||
maxPrice: "300",
|
||||
brand: [],
|
||||
memory: "",
|
||||
color: ""
|
||||
});
|
||||
|
||||
type FilterForm = {
|
||||
core: number;
|
||||
sort: string;
|
||||
switch: boolean;
|
||||
};
|
||||
|
||||
const filterForm = reactive<FilterForm>({
|
||||
core: 0,
|
||||
sort: "none",
|
||||
switch: false
|
||||
});
|
||||
|
||||
function onOptionsChange(val: number) {
|
||||
console.log(val);
|
||||
filterForm.core = val;
|
||||
}
|
||||
|
||||
function onSortChange(val: string) {
|
||||
console.log(val);
|
||||
filterForm.sort = val;
|
||||
}
|
||||
|
||||
function onSwitchChange(val: boolean) {
|
||||
console.log(val);
|
||||
filterForm.switch = val;
|
||||
}
|
||||
</script>
|
||||
@@ -310,6 +310,11 @@ const data = computed<Item[]>(() => {
|
||||
label: t("拖拽"),
|
||||
icon: "drag-move-line",
|
||||
path: "/pages/demo/data/draggable"
|
||||
},
|
||||
{
|
||||
label: t("筛选栏"),
|
||||
icon: "filter-line",
|
||||
path: "/pages/demo/data/filter-bar"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<view class="cl-filter-bar">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineOptions({
|
||||
name: "cl-filter-bar"
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cl-filter-bar {
|
||||
@apply flex flex-row;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<view class="cl-filter-item" :class="[pt.className]" @tap="onTap">
|
||||
<slot>
|
||||
<cl-text
|
||||
:pt="{
|
||||
className: parseClass([
|
||||
[isActive, '!text-primary-500'],
|
||||
'text-center',
|
||||
pt.label?.className
|
||||
])
|
||||
}"
|
||||
>{{ text }}</cl-text
|
||||
>
|
||||
|
||||
<!-- 排序 -->
|
||||
<cl-icon
|
||||
v-if="type == 'sort' && sort != 'none'"
|
||||
:name="`sort-${sort}`"
|
||||
:pt="{
|
||||
className: 'ml-1'
|
||||
}"
|
||||
></cl-icon>
|
||||
|
||||
<!-- 下拉框 -->
|
||||
<cl-icon
|
||||
v-if="type == 'select'"
|
||||
name="arrow-down-s-line"
|
||||
:pt="{
|
||||
className: 'ml-1'
|
||||
}"
|
||||
></cl-icon>
|
||||
</slot>
|
||||
</view>
|
||||
|
||||
<cl-select
|
||||
v-model="selectValue"
|
||||
ref="selectRef"
|
||||
:show-trigger="false"
|
||||
:options="options"
|
||||
></cl-select>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { parsePt, parseClass } from "@/cool";
|
||||
import { computed, onMounted, ref, watch, type PropType } from "vue";
|
||||
import type { PassThroughProps, ClFilterItemType, ClSelectOption } from "../../types";
|
||||
|
||||
defineOptions({
|
||||
name: "cl-filter-item"
|
||||
});
|
||||
|
||||
const props = defineProps({
|
||||
pt: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
value: {
|
||||
type: [String, Number, Boolean, Array] as PropType<any>,
|
||||
required: true
|
||||
},
|
||||
type: {
|
||||
type: String as PropType<ClFilterItemType>,
|
||||
default: "switch"
|
||||
},
|
||||
options: {
|
||||
type: Array as PropType<ClSelectOption[]>,
|
||||
default: () => []
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(["change"]);
|
||||
|
||||
// 透传样式类型
|
||||
type PassThrough = {
|
||||
className?: string;
|
||||
label?: PassThroughProps;
|
||||
};
|
||||
|
||||
// 解析透传样式
|
||||
const pt = computed(() => parsePt<PassThrough>(props.pt));
|
||||
|
||||
// select组件的ref引用,用于调用select的方法
|
||||
const selectRef = ref<ClSelectComponentPublicInstance | null>(null);
|
||||
|
||||
// switch类型的激活状态
|
||||
const isActive = ref(false);
|
||||
|
||||
// sort类型的排序状态,可为"asc"、"desc"、"none"
|
||||
const sort = ref("none");
|
||||
|
||||
// select类型的当前选中值
|
||||
const selectValue = ref<any | null>(null);
|
||||
|
||||
// 根据类型动态计算显示文本
|
||||
const text = computed(() => {
|
||||
// 如果是select类型,显示选中项的label
|
||||
if (props.type == "select") {
|
||||
return props.options.find((e) => e.value == selectValue.value)?.label ?? "";
|
||||
} else {
|
||||
// 其他类型直接显示label
|
||||
return props.label;
|
||||
}
|
||||
});
|
||||
|
||||
// 点击事件,根据不同类型处理
|
||||
function onTap() {
|
||||
// 排序类型,切换排序状态
|
||||
if (props.type == "sort") {
|
||||
if (sort.value == "asc") {
|
||||
sort.value = "desc";
|
||||
} else if (sort.value == "desc") {
|
||||
sort.value = "none";
|
||||
} else {
|
||||
sort.value = "asc";
|
||||
}
|
||||
emit("change", sort.value);
|
||||
}
|
||||
|
||||
// 开关类型,切换激活状态
|
||||
if (props.type == "switch") {
|
||||
isActive.value = !isActive.value;
|
||||
emit("change", isActive.value);
|
||||
}
|
||||
|
||||
// 选择类型,打开select组件
|
||||
if (props.type == "select") {
|
||||
// 打开select弹窗,选择后回调
|
||||
selectRef.value!.open((val) => {
|
||||
emit("change", val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时,监听props.value变化并同步到本地状态
|
||||
onMounted(() => {
|
||||
watch(
|
||||
computed(() => props.value!),
|
||||
(val: any) => {
|
||||
switch (props.type) {
|
||||
case "select":
|
||||
// select类型,同步选中值
|
||||
selectValue.value = val as any;
|
||||
break;
|
||||
case "switch":
|
||||
// switch类型,同步激活状态
|
||||
isActive.value = val as boolean;
|
||||
break;
|
||||
case "sort":
|
||||
// sort类型,同步排序状态
|
||||
sort.value = val as string;
|
||||
break;
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cl-filter-item {
|
||||
@apply flex flex-row flex-1 justify-center items-center h-[72rpx];
|
||||
}
|
||||
</style>
|
||||
@@ -172,3 +172,12 @@ export type ClFormValidateResult = {
|
||||
};
|
||||
|
||||
export type ClFormLabelPosition = "left" | "top" | "right";
|
||||
|
||||
export type ClFilterItemType = "switch" | "sort" | "select";
|
||||
|
||||
export type ClFilterItem = {
|
||||
label: string;
|
||||
value: any;
|
||||
type: ClFilterItemType;
|
||||
options?: ClSelectOption[];
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user