|
|
@@ -0,0 +1,197 @@
|
|
|
+<template>
|
|
|
+ <div class="game-select-wrapper">
|
|
|
+ <a-tree-select
|
|
|
+ :model-value="modelValue"
|
|
|
+ @update:model-value="onUpdate"
|
|
|
+ :data="gameListTree"
|
|
|
+ tree-checked-strategy="child"
|
|
|
+ :tree-checkable="multiple"
|
|
|
+ :max-tag-count="1"
|
|
|
+ :fieldNames="{ title: 'name', key: 'id' }"
|
|
|
+ allow-search
|
|
|
+ allow-clear
|
|
|
+ :check-strictly="props.multiple"
|
|
|
+ :filter-tree-node="filterTreeNode"
|
|
|
+ :treeProps="{
|
|
|
+ defaultExpandedKeys: [],
|
|
|
+ expandedKeys: expandedKeys,
|
|
|
+ onExpand: expandTreeNode,
|
|
|
+ }"
|
|
|
+ placeholder="媒体和组织"
|
|
|
+ >
|
|
|
+ <template #header v-if="multiple && showFooter">
|
|
|
+ <div class="tree-select-header">
|
|
|
+ <a-space>
|
|
|
+ <a-button type="primary" size="mini" @click="handleSelectAll">
|
|
|
+ 全选
|
|
|
+ </a-button>
|
|
|
+ <a-button type="outline" size="mini" @click="handleInvertSelect">
|
|
|
+ 反选
|
|
|
+ </a-button>
|
|
|
+ <a-button type="dashed" size="mini" @click="handleClear">
|
|
|
+ 清空
|
|
|
+ </a-button>
|
|
|
+ </a-space>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </a-tree-select>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, onMounted, defineProps, defineEmits } from "vue";
|
|
|
+import commonApi from "../../views/v1/api/common";
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ modelValue: {
|
|
|
+ type: [String, Number, Array],
|
|
|
+ default: "",
|
|
|
+ },
|
|
|
+ multiple: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false,
|
|
|
+ },
|
|
|
+ showFooter: {
|
|
|
+ type: Boolean,
|
|
|
+ default: true,
|
|
|
+ },
|
|
|
+});
|
|
|
+
|
|
|
+const expandedKeys = ref([]);
|
|
|
+const searchText = ref(""); // 存储当前搜索文本
|
|
|
+const emit = defineEmits(["update:modelValue", "change"]);
|
|
|
+const onUpdate = (val) => {
|
|
|
+ emit("update:modelValue", val);
|
|
|
+ emit("change", val);
|
|
|
+};
|
|
|
+const gameListTree = ref([]);
|
|
|
+
|
|
|
+const fetchGameList = async () => {
|
|
|
+ // 这里替换为你的实际API请求
|
|
|
+ // const response = await api.getGameList()
|
|
|
+ // gameListTree.value = response.data
|
|
|
+ const response = await commonApi.getMediaOrgTreeKvApi();
|
|
|
+ gameListTree.value = response.data;
|
|
|
+
|
|
|
+ if (!props.multiple) {
|
|
|
+ gameListTree.value = response.data.map((item) => {
|
|
|
+ return {
|
|
|
+ ...item,
|
|
|
+ disabled: true,
|
|
|
+ children: item.children || [],
|
|
|
+ };
|
|
|
+ });
|
|
|
+ }
|
|
|
+};
|
|
|
+const filterTreeNode = (inputText, node) => {
|
|
|
+ // 更新搜索文本
|
|
|
+ searchText.value = inputText || "";
|
|
|
+
|
|
|
+ if (inputText) {
|
|
|
+ const filterData =
|
|
|
+ node.name.toLowerCase().indexOf(inputText.toLowerCase()) > -1;
|
|
|
+ if (filterData) {
|
|
|
+ if (!expandedKeys.value.includes(node.parent_id)) {
|
|
|
+ expandedKeys.value.push(node.parent_id);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return filterData;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const expandTreeNode = (expandKeys) => {
|
|
|
+ expandedKeys.value = expandKeys;
|
|
|
+};
|
|
|
+
|
|
|
+// 获取所有子节点的ID(支持搜索过滤)
|
|
|
+const getAllChildIds = (tree, filterText = "") => {
|
|
|
+ const ids = [];
|
|
|
+ const traverse = (nodes) => {
|
|
|
+ nodes.forEach((node) => {
|
|
|
+ // 检查节点是否匹配搜索条件
|
|
|
+ const matchFilter =
|
|
|
+ !filterText ||
|
|
|
+ node.name.toLowerCase().indexOf(filterText.toLowerCase()) > -1;
|
|
|
+
|
|
|
+ if (node.children && node.children.length > 0) {
|
|
|
+ traverse(node.children);
|
|
|
+ } else if (matchFilter) {
|
|
|
+ // 只有叶子节点且匹配搜索条件时才添加
|
|
|
+ ids.push(node.id);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ };
|
|
|
+ traverse(tree);
|
|
|
+ return ids;
|
|
|
+};
|
|
|
+
|
|
|
+// 全选
|
|
|
+const handleSelectAll = () => {
|
|
|
+ if (!props.multiple) return;
|
|
|
+ const currentSelected = Array.isArray(props.modelValue)
|
|
|
+ ? props.modelValue
|
|
|
+ : [];
|
|
|
+ const filterIds = getAllChildIds(gameListTree.value, searchText.value);
|
|
|
+
|
|
|
+ if (searchText.value) {
|
|
|
+ // 有搜索条件:保留原有选中项,添加搜索结果
|
|
|
+ const newIds = [...new Set([...currentSelected, ...filterIds])];
|
|
|
+ onUpdate(newIds);
|
|
|
+ } else {
|
|
|
+ // 无搜索条件:全选所有
|
|
|
+ onUpdate(filterIds);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 反选
|
|
|
+const handleInvertSelect = () => {
|
|
|
+ if (!props.multiple) return;
|
|
|
+ const currentSelected = Array.isArray(props.modelValue)
|
|
|
+ ? props.modelValue
|
|
|
+ : [];
|
|
|
+ const filterIds = getAllChildIds(gameListTree.value, searchText.value);
|
|
|
+
|
|
|
+ if (searchText.value) {
|
|
|
+ // 有搜索条件:只在搜索结果范围内反选,保留其他已选项
|
|
|
+ const otherSelected = currentSelected.filter(
|
|
|
+ (id) => !filterIds.includes(id)
|
|
|
+ );
|
|
|
+ const invertedInFilter = filterIds.filter(
|
|
|
+ (id) => !currentSelected.includes(id)
|
|
|
+ );
|
|
|
+ onUpdate([...otherSelected, ...invertedInFilter]);
|
|
|
+ } else {
|
|
|
+ // 无搜索条件:全局反选
|
|
|
+ const invertedIds = filterIds.filter((id) => !currentSelected.includes(id));
|
|
|
+ onUpdate(invertedIds);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 清空
|
|
|
+const handleClear = () => {
|
|
|
+ if (!props.multiple) return;
|
|
|
+ onUpdate([]);
|
|
|
+};
|
|
|
+
|
|
|
+onMounted(fetchGameList);
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.game-select-wrapper {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.tree-select-header {
|
|
|
+ padding: 8px 12px;
|
|
|
+ border-bottom: 1px solid var(--color-border-2);
|
|
|
+}
|
|
|
+
|
|
|
+.tree-select-header :deep(.arco-btn-text) {
|
|
|
+ color: var(--color-text-2);
|
|
|
+}
|
|
|
+
|
|
|
+.tree-select-header :deep(.arco-btn-text:hover) {
|
|
|
+ color: rgb(var(--primary-6));
|
|
|
+ background-color: transparent;
|
|
|
+}
|
|
|
+</style>
|