mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2026-05-28 06:35:47 +08:00
✨ 重构角色筛选组件
This commit is contained in:
@@ -1,43 +1,44 @@
|
||||
<!-- 选项组件 -->
|
||||
<template>
|
||||
<div class="uav-select-chips-box">
|
||||
<v-chip-group :model-value="result" multiple>
|
||||
<!-- ALL -->
|
||||
<v-chip
|
||||
:size="props.size"
|
||||
:value="'__all__'"
|
||||
class="uav-scb-all"
|
||||
filter
|
||||
title="全部"
|
||||
variant="elevated"
|
||||
@click="toggleAll()"
|
||||
>
|
||||
<template #filter>
|
||||
<!-- ALL -->
|
||||
<v-chip
|
||||
key="all"
|
||||
:size="props.size"
|
||||
class="uav-scb-all"
|
||||
title="全部"
|
||||
variant="elevated"
|
||||
@click.stop="toggleAll"
|
||||
>
|
||||
<template #prepend>
|
||||
<div :class="{ active: isAllSelected }" class="icon-wrap">
|
||||
<v-icon color="var(--tgc-od-green)">mdi-check</v-icon>
|
||||
</template>
|
||||
<slot :selected="selectAll" name="all">All</slot>
|
||||
</v-chip>
|
||||
</div>
|
||||
</template>
|
||||
<div :class="{ shifted: isAllSelected }" class="all-label">
|
||||
<slot :selected="isAllSelected" name="all">All</slot>
|
||||
</div>
|
||||
</v-chip>
|
||||
<v-chip-group v-model="result" filter multiple>
|
||||
<!-- Options -->
|
||||
<v-chip
|
||||
v-for="(item, idx) in props.items"
|
||||
:key="idx"
|
||||
v-for="item in props.items"
|
||||
:key="item.value"
|
||||
:size="props.size"
|
||||
:title="getItemTitle(item)"
|
||||
:value="getItemValue(item)"
|
||||
:title="item.title"
|
||||
:value="item.value"
|
||||
class="uav-scb-item"
|
||||
filter
|
||||
variant="elevated"
|
||||
@click="selectItem(item)"
|
||||
>
|
||||
<template #filter>
|
||||
<v-icon color="var(--tgc-od-blue)">mdi-check</v-icon>
|
||||
</template>
|
||||
<slot :selected="result.includes(getItemValue(item))" name="item">
|
||||
<template v-if="typeof item === 'string'">{{ item }}</template>
|
||||
<template v-else>
|
||||
<slot :selected="result.includes(item.value)" name="item">
|
||||
<div class="uav-scb-inner">
|
||||
<TMiImg v-if="item.icon" :src="item.icon" alt="icon" />
|
||||
<span>{{ item.label }}</span>
|
||||
</template>
|
||||
</div>
|
||||
</slot>
|
||||
</v-chip>
|
||||
</v-chip-group>
|
||||
@@ -46,25 +47,36 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import TMiImg from "@comp/app/t-mi-img.vue";
|
||||
import { computed, defineModel, defineProps, shallowRef, triggerRef, watch } from "vue";
|
||||
import { computed, defineProps, ref, watch } from "vue";
|
||||
|
||||
export type UavSelectChipsItem = {
|
||||
label: string;
|
||||
/** 渲染文本 */
|
||||
label?: string;
|
||||
/** 渲染图标 */
|
||||
icon?: string;
|
||||
title?: string;
|
||||
/** 提示文本 */
|
||||
title: string;
|
||||
/** 选项值 */
|
||||
value: string;
|
||||
};
|
||||
type UavSelectChipsProps = {
|
||||
/** 选中 */
|
||||
selected: Array<string>;
|
||||
/** 选项 */
|
||||
items: Array<string | UavSelectChipsItem>;
|
||||
items: Array<UavSelectChipsItem>;
|
||||
/** 尺寸 */
|
||||
size?: "x-small" | "small" | "default" | "large" | "x-large" | number;
|
||||
};
|
||||
type UavSelectChipsEmits = (e: "chip-select", v: Array<string>) => void;
|
||||
|
||||
const props = withDefaults(defineProps<UavSelectChipsProps>(), { size: "default" });
|
||||
const result = shallowRef<Array<string>>([]);
|
||||
const model = defineModel<Array<string>>({ default: [] });
|
||||
const selectAll = computed<boolean>(() => result.value.includes("__all__"));
|
||||
const emits = defineEmits<UavSelectChipsEmits>();
|
||||
|
||||
const result = ref<Array<string>>(props.selected);
|
||||
const isAllSelected = computed<boolean>(() => {
|
||||
if (!props.items || props.items.length === 0) return false;
|
||||
return props.items.every((i) => result.value.includes(i.value.toString()));
|
||||
});
|
||||
const iconHeight = computed<string>(() => {
|
||||
switch (props.size) {
|
||||
case "x-small":
|
||||
@@ -82,41 +94,22 @@ const iconHeight = computed<string>(() => {
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.selected,
|
||||
(v) => (result.value = [...v]),
|
||||
{ deep: true },
|
||||
);
|
||||
watch(
|
||||
() => result.value,
|
||||
() => {
|
||||
model.value = result.value.filter((i) => i !== "__all__");
|
||||
},
|
||||
() => emits("chip-select", result.value),
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
async function toggleAll(): Promise<void> {
|
||||
if (result.value.includes("__all__")) {
|
||||
if (isAllSelected.value) {
|
||||
result.value = [];
|
||||
} else {
|
||||
result.value = ["__all__", ...props.items.map(getItemValue)];
|
||||
}
|
||||
}
|
||||
|
||||
function getItemValue(item: UavSelectChipsItem | string): string {
|
||||
if (typeof item === "string") return item;
|
||||
return item.value;
|
||||
}
|
||||
|
||||
function getItemTitle(item: UavSelectChipsItem | string): string | undefined {
|
||||
if (typeof item === "string") return item;
|
||||
return item.title;
|
||||
}
|
||||
|
||||
function selectItem(item: UavSelectChipsItem | string): void {
|
||||
const itemValue = getItemValue(item);
|
||||
if (result.value.includes(itemValue)) {
|
||||
result.value = result.value.filter((i) => i !== "__all__" && i !== itemValue);
|
||||
} else {
|
||||
result.value.push(itemValue);
|
||||
if (result.value.length === props.items.length) {
|
||||
result.value.push("__all__");
|
||||
}
|
||||
triggerRef(result);
|
||||
result.value = props.items.map((i) => i.value);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -133,12 +126,53 @@ function selectItem(item: UavSelectChipsItem | string): void {
|
||||
|
||||
.uav-scb-all {
|
||||
@include github-styles.github-tag-dark-gen(#41b883);
|
||||
|
||||
--icon-size: 16px;
|
||||
--icon-gap: 2px;
|
||||
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
.icon-wrap {
|
||||
display: inline-flex;
|
||||
overflow: hidden;
|
||||
width: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 0;
|
||||
opacity: 0;
|
||||
transition:
|
||||
width 0.3s ease-in-out,
|
||||
opacity 0.3s ease-in-out,
|
||||
margin-right 0.3s ease-in-out;
|
||||
|
||||
&.active {
|
||||
width: var(--icon-size);
|
||||
margin-right: var(--icon-gap);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.all-label {
|
||||
display: inline-block;
|
||||
transform: translateX(0);
|
||||
transition: transform var(--anim-duration) var(--anim-ease);
|
||||
will-change: transform;
|
||||
}
|
||||
}
|
||||
|
||||
.uav-scb-item {
|
||||
@include github-styles.github-tag-dark-gen(#548af7);
|
||||
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.uav-scb-inner {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
column-gap: 2px;
|
||||
|
||||
img {
|
||||
position: relative;
|
||||
|
||||
191
src/components/userAvatar/uav-select.vue
Normal file
191
src/components/userAvatar/uav-select.vue
Normal file
@@ -0,0 +1,191 @@
|
||||
<!-- 角色筛选组件 -->
|
||||
<template>
|
||||
<v-bottom-sheet v-model="visible">
|
||||
<div class="uav-select-container">
|
||||
<div class="uav-select-item">
|
||||
<div class="uav-select-title">星级</div>
|
||||
<div class="uav-select-props">
|
||||
<UavSelectChips
|
||||
:items="starOpts"
|
||||
:selected="model.star"
|
||||
size="small"
|
||||
@chipSelect="handleStarSelect"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="uav-select-item">
|
||||
<div class="uav-select-title">武器</div>
|
||||
<div class="uav-select-props">
|
||||
<UavSelectChips
|
||||
:items="weaponOpts"
|
||||
:selected="model.weapon"
|
||||
size="small"
|
||||
@chipSelect="handleWeaponSelect"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="uav-select-item">
|
||||
<div class="uav-select-title">元素</div>
|
||||
<div class="uav-select-props">
|
||||
<UavSelectChips
|
||||
:items="elementOpts"
|
||||
:selected="model.element"
|
||||
size="small"
|
||||
@chipSelect="handleElementSelect"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="uav-select-item">
|
||||
<div class="uav-select-title">地区</div>
|
||||
<div class="uav-select-props">
|
||||
<UavSelectChips
|
||||
:items="areaOpts"
|
||||
:selected="model.area"
|
||||
size="small"
|
||||
@chipSelect="handleAreaSelect"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="uav-select-acts">
|
||||
<v-btn class="uav-act-btn" prepend-icon="mdi-check" variant="elevated" @click="onConfirm()">
|
||||
确定
|
||||
</v-btn>
|
||||
<v-btn class="uav-act-btn" prepend-icon="mdi-cancel" variant="elevated" @click="onCancel()">
|
||||
取消
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</v-bottom-sheet>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import UavSelectChips, { type UavSelectChipsItem } from "@comp/userAvatar/uav-select-chips.vue";
|
||||
import { ref } from "vue";
|
||||
|
||||
/** 返回数据 */
|
||||
export type UavSelectModel = {
|
||||
/** 星级 */
|
||||
star: Array<string>;
|
||||
/** 武器 */
|
||||
weapon: Array<string>;
|
||||
/** 元素 */
|
||||
element: Array<string>;
|
||||
/** 地区 */
|
||||
area: Array<string>;
|
||||
};
|
||||
|
||||
type UavSelectEmits = (e: "select", v: UavSelectModel) => void;
|
||||
|
||||
const starOpts: Array<UavSelectChipsItem> = [
|
||||
{ label: "⭐⭐⭐⭐", value: "4", title: "四星" },
|
||||
{ label: "⭐⭐⭐⭐⭐", value: "5", title: "五星" },
|
||||
];
|
||||
const weaponOpts: Array<UavSelectChipsItem> = ["单手剑", "双手剑", "弓", "法器", "长柄武器"].map(
|
||||
(i) => ({ label: i, value: i, title: i, icon: `/icon/weapon/${i}.webp` }),
|
||||
);
|
||||
const elementOpts: Array<UavSelectChipsItem> = ["冰", "岩", "水", "火", "草", "雷", "风"].map(
|
||||
(i) => ({ label: i, value: i, title: `${i}元素`, icon: `/icon/element/${i}元素.webp` }),
|
||||
);
|
||||
const areaOpts: Array<UavSelectChipsItem> = [
|
||||
"未知",
|
||||
"蒙德",
|
||||
"璃月",
|
||||
"主角",
|
||||
"愚人众",
|
||||
"稻妻",
|
||||
"游侠",
|
||||
"须弥",
|
||||
"枫丹",
|
||||
"纳塔",
|
||||
"至冬",
|
||||
"寰宇劫灭",
|
||||
"挪德卡莱",
|
||||
].map((i) => ({ label: i, value: i, title: i }));
|
||||
|
||||
const emits = defineEmits<UavSelectEmits>();
|
||||
|
||||
const starSelected = ref<Array<string>>([]);
|
||||
const weaponSelected = ref<Array<string>>([]);
|
||||
const elementSelected = ref<Array<string>>([]);
|
||||
const areaSelected = ref<Array<string>>([]);
|
||||
|
||||
const model = defineModel<UavSelectModel>({
|
||||
default: { star: [], weapon: [], area: [], element: [] },
|
||||
});
|
||||
const visible = defineModel<boolean>("show");
|
||||
|
||||
function onCancel(): void {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
async function onConfirm(): Promise<void> {
|
||||
model.value = {
|
||||
star: starSelected.value,
|
||||
weapon: weaponSelected.value,
|
||||
element: elementSelected.value,
|
||||
area: areaSelected.value,
|
||||
};
|
||||
// await nextTick();
|
||||
emits("select", model.value);
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
function handleStarSelect(e: Array<string>): void {
|
||||
console.log("starSelect", e);
|
||||
starSelected.value = e;
|
||||
}
|
||||
|
||||
function handleWeaponSelect(e: Array<string>): void {
|
||||
console.log("weaponSelect", e);
|
||||
weaponSelected.value = e;
|
||||
}
|
||||
|
||||
function handleElementSelect(e: Array<string>): void {
|
||||
console.log("elementSelect", e);
|
||||
elementSelected.value = e;
|
||||
}
|
||||
|
||||
function handleAreaSelect(e: Array<string>): void {
|
||||
console.log("areaSelect", e);
|
||||
areaSelected.value = e;
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.uav-select-container {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
border-radius: 4px;
|
||||
background: var(--app-page-bg);
|
||||
}
|
||||
|
||||
.uav-select-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
color: var(--common-text-title);
|
||||
column-gap: 8px;
|
||||
}
|
||||
|
||||
.uav-select-acts {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 8px;
|
||||
column-gap: 8px;
|
||||
}
|
||||
|
||||
.uav-act-btn {
|
||||
background: var(--tgc-btn-1);
|
||||
color: var(--btn-text);
|
||||
font-family: var(--font-text);
|
||||
}
|
||||
</style>
|
||||
@@ -6,7 +6,7 @@
|
||||
<img alt="icon" src="/source/UI/userAvatar.webp" />
|
||||
<span>我的角色</span>
|
||||
<v-btn class="uc-top-btn" variant="elevated" @click="showSelect = true">筛选角色</v-btn>
|
||||
<v-btn class="uc-top-btn" variant="elevated" @click="resetSelect = true">重置筛选</v-btn>
|
||||
<v-btn class="uc-top-btn" variant="elevated" @click="resetList()">重置筛选</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
<template #append>
|
||||
@@ -138,15 +138,16 @@
|
||||
@to-next="handleSwitch"
|
||||
@to-avatar="selectRole"
|
||||
/>
|
||||
<TwoSelectC v-model="showSelect" v-model:reset="resetSelect" @select-c="handleSelect" />
|
||||
<!-- <TwoSelectC v-model="showSelect" v-model:reset="resetSelect" @select-c="handleSelect" />-->
|
||||
<UavSelect v-model:show="showSelect" :model-value="selectOpts" @select="handleSelect" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import showDialog from "@comp/func/dialog.js";
|
||||
import showLoading from "@comp/func/loading.js";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import TwoSelectC, { type SelectedCValue } from "@comp/pageWiki/two-select-c.vue";
|
||||
import TuaAvatarBox from "@comp/userAvatar/tua-avatar-box.vue";
|
||||
import TuaDetailOverlay from "@comp/userAvatar/tua-detail-overlay.vue";
|
||||
import UavSelect, { type UavSelectModel } from "@comp/userAvatar/uav-select.vue";
|
||||
import recordReq from "@req/recordReq.js";
|
||||
import TSUserAvatar from "@Sqlm/userAvatar.js";
|
||||
import useAppStore from "@store/app.js";
|
||||
@@ -181,7 +182,6 @@ const showOverlay = ref<boolean>(false);
|
||||
const selectIndex = ref<number>(0);
|
||||
const showSelect = ref<boolean>(false);
|
||||
const showMode = ref<"classic" | "card" | "dev">("dev");
|
||||
const resetSelect = ref<boolean>(false);
|
||||
const uidCur = ref<string>();
|
||||
|
||||
// 排序
|
||||
@@ -192,6 +192,7 @@ const isConstUp = ref<boolean | null>(null);
|
||||
const uidList = shallowRef<Array<string>>([]);
|
||||
const roleOverview = shallowRef<Array<OverviewItem>>([]);
|
||||
const roleList = shallowRef<Array<TGApp.Sqlite.Character.TableTrans>>([]);
|
||||
const selectOpts = shallowRef<UavSelectModel>({ star: [], weapon: [], area: [], element: [] });
|
||||
const selectedList = shallowRef<Array<TGApp.Sqlite.Character.TableTrans>>([]);
|
||||
const dataVal = shallowRef<TGApp.Sqlite.Character.TableTrans>();
|
||||
const enableShare = computed<boolean>(
|
||||
@@ -209,25 +210,6 @@ onMounted(async () => {
|
||||
await showLoading.end();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => resetSelect.value,
|
||||
() => {
|
||||
if (resetSelect.value) {
|
||||
selectedList.value = getOrderedList(roleList.value);
|
||||
showSnackbar.success("已重置筛选条件");
|
||||
if (!dataVal.value) return;
|
||||
selectIndex.value = selectedList.value.indexOf(dataVal.value);
|
||||
if (selectIndex.value === -1) {
|
||||
dataVal.value = selectedList.value[0];
|
||||
selectIndex.value = 0;
|
||||
}
|
||||
isLevelUp.value = null;
|
||||
isFetterUp.value = null;
|
||||
isConstUp.value = null;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => showMode.value,
|
||||
() => {
|
||||
@@ -307,6 +289,21 @@ function getSortDesc(value: boolean | null): string {
|
||||
}
|
||||
}
|
||||
|
||||
function resetList(): void {
|
||||
selectedList.value = getOrderedList(roleList.value);
|
||||
showSnackbar.success("已重置筛选条件");
|
||||
if (!dataVal.value) return;
|
||||
selectIndex.value = selectedList.value.indexOf(dataVal.value);
|
||||
if (selectIndex.value === -1) {
|
||||
dataVal.value = selectedList.value[0];
|
||||
selectIndex.value = 0;
|
||||
}
|
||||
isLevelUp.value = null;
|
||||
isFetterUp.value = null;
|
||||
isConstUp.value = null;
|
||||
selectOpts.value = { star: [], weapon: [], area: [], element: [] };
|
||||
}
|
||||
|
||||
function getOrderedList(
|
||||
data: Array<TGApp.Sqlite.Character.TableTrans>,
|
||||
): Array<TGApp.Sqlite.Character.TableTrans> {
|
||||
@@ -502,13 +499,13 @@ function selectRole(role: TGApp.Sqlite.Character.TableTrans): void {
|
||||
if (!showOverlay.value) showOverlay.value = true;
|
||||
}
|
||||
|
||||
function handleSelect(val: SelectedCValue) {
|
||||
showSelect.value = false;
|
||||
function handleSelect(val: UavSelectModel): void {
|
||||
selectOpts.value = val;
|
||||
const filterC = AppCharacterData.filter((avatar) => {
|
||||
if (!val.star.includes(avatar.star)) return false;
|
||||
if (!val.weapon.includes(avatar.weapon)) return false;
|
||||
if (!val.elements.includes(avatar.element)) return false;
|
||||
if (!val.area.includes(avatar.area)) return false;
|
||||
if (val.star.length > 0 && !val.star.includes(avatar.star.toString())) return false;
|
||||
if (val.weapon.length > 0 && !val.weapon.includes(avatar.weapon)) return false;
|
||||
if (val.element.length > 0 && !val.element.includes(avatar.element)) return false;
|
||||
if (val.area.length > 0 && !val.area.includes(avatar.area)) return false;
|
||||
return roleList.value.find(
|
||||
(role) =>
|
||||
role.avatar.id === avatar.id && getZhElement(role.avatar.element) === avatar.element,
|
||||
|
||||
Reference in New Issue
Block a user