feat: 选择材料-子类支持多选组合筛选
子类从单选改为多选,可同时勾选多个子类(带✓标记),下方材料列表 显示所选子类的合集(客户端组合过滤,切换即时生效)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3bbafc99d7
commit
7f03b11a95
|
|
@ -33,28 +33,31 @@
|
|||
@click="selectMajor(g.major)"
|
||||
>{{ g.major }}</a-tag>
|
||||
</div>
|
||||
<!-- 级联:子类 -->
|
||||
<!-- 级联:子类(可多选组合) -->
|
||||
<div class="cascade-row" v-if="currentSubs.length">
|
||||
<span class="cascade-lbl">子类</span>
|
||||
<a-tag
|
||||
:color="sub === null ? '#b4232a' : 'default'"
|
||||
:color="!subs.length ? '#b4232a' : 'default'"
|
||||
class="cas-tag"
|
||||
@click="selectSub(null)"
|
||||
@click="clearSubs"
|
||||
>全部</a-tag>
|
||||
<a-tag
|
||||
v-for="s in currentSubs"
|
||||
:key="s"
|
||||
:color="sub === s ? '#b4232a' : 'default'"
|
||||
:color="subs.includes(s) ? '#b4232a' : 'default'"
|
||||
class="cas-tag"
|
||||
@click="selectSub(s)"
|
||||
>{{ s }}</a-tag>
|
||||
@click="toggleSub(s)"
|
||||
>
|
||||
<CheckOutlined v-if="subs.includes(s)" /> {{ s }}
|
||||
</a-tag>
|
||||
<span v-if="subs.length" class="multi-tip">已选 {{ subs.length }} 个子类(组合)</span>
|
||||
</div>
|
||||
|
||||
<a-divider style="margin: 10px 0" />
|
||||
|
||||
<!-- 当前类别材料 -->
|
||||
<div class="list-head">
|
||||
<span>{{ major }}<template v-if="sub"> / {{ sub }}</template> · 共 {{ list.length }} 种</span>
|
||||
<span>{{ major }}<template v-if="subs.length"> / {{ subs.join('、') }}</template> · 共 {{ list.length }} 种</span>
|
||||
<a-button type="primary" size="small" :disabled="checkedCount === 0" @click="addSelected">
|
||||
批量添加所选({{ checkedCount }})
|
||||
</a-button>
|
||||
|
|
@ -103,6 +106,7 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { CheckOutlined } from '@ant-design/icons-vue';
|
||||
import { MATERIAL_CATEGORIES, HEALTH_GRADES, ENV_GRADES } from '@airpredict/shared';
|
||||
import { listMaterials, type Material } from '../api/materials';
|
||||
|
||||
|
|
@ -119,9 +123,16 @@ const scope = ref<'public' | 'self'>('public');
|
|||
const healthGrade = ref<string | undefined>(undefined);
|
||||
const envGrade = ref<string | undefined>(undefined);
|
||||
const major = ref<string>('');
|
||||
const sub = ref<string | null>(null);
|
||||
const list = ref<Material[]>([]);
|
||||
const subs = ref<string[]>([]); // 多选子类(组合筛选)
|
||||
const fullList = ref<Material[]>([]); // 当前大类全部材料(服务端按健康/环保筛过)
|
||||
const loading = ref(false);
|
||||
|
||||
// 按已选子类做客户端组合过滤;未选=显示整个大类
|
||||
const list = computed(() => {
|
||||
if (!subs.value.length) return fullList.value;
|
||||
const set = new Set(subs.value.map((s) => `${major.value}/${s}`));
|
||||
return fullList.value.filter((m) => set.has(m.category));
|
||||
});
|
||||
const rowState = reactive<Record<string, { checked: boolean; area: number | null }>>({});
|
||||
|
||||
const picked = computed(() => new Set(props.existingIds));
|
||||
|
|
@ -157,29 +168,33 @@ function healthColor(g: string) {
|
|||
|
||||
function selectMajor(m: string) {
|
||||
major.value = m;
|
||||
sub.value = null;
|
||||
subs.value = [];
|
||||
reload();
|
||||
}
|
||||
function selectSub(s: string | null) {
|
||||
sub.value = s;
|
||||
reload();
|
||||
function toggleSub(s: string) {
|
||||
const i = subs.value.indexOf(s);
|
||||
if (i >= 0) subs.value.splice(i, 1);
|
||||
else subs.value.push(s);
|
||||
}
|
||||
function clearSubs() {
|
||||
subs.value = [];
|
||||
}
|
||||
|
||||
// 切大类/健康/环保/库时重新拉整个大类的材料(子类组合在客户端过滤)
|
||||
async function reload() {
|
||||
if (!major.value) return;
|
||||
loading.value = true;
|
||||
for (const k of Object.keys(rowState)) delete rowState[k];
|
||||
try {
|
||||
const category = sub.value ? `${major.value}/${sub.value}` : major.value;
|
||||
const res = await listMaterials({
|
||||
category,
|
||||
category: major.value,
|
||||
healthGrade: healthGrade.value,
|
||||
envGrade: envGrade.value,
|
||||
scope: scope.value,
|
||||
page: 1,
|
||||
pageSize: 300,
|
||||
});
|
||||
list.value = res.items;
|
||||
fullList.value = res.items;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
|
|
@ -207,7 +222,7 @@ watch(
|
|||
(o) => {
|
||||
if (o) {
|
||||
if (!major.value) major.value = tree.value[0]?.major || '';
|
||||
sub.value = null;
|
||||
subs.value = [];
|
||||
reload();
|
||||
}
|
||||
},
|
||||
|
|
@ -220,6 +235,7 @@ watch(
|
|||
.cascade-row { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
|
||||
.cascade-lbl { color: #999; font-size: 12px; width: 32px; flex-shrink: 0; }
|
||||
.cas-tag { cursor: pointer; user-select: none; margin: 0; }
|
||||
.multi-tip { color: #b4232a; font-size: 12px; margin-left: 8px; }
|
||||
.list-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; color: #555; }
|
||||
.added { color: #999; }
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Reference in New Issue