|
1 | | -import { defineComponent, PropType, ref, computed } from 'vue'; |
| 1 | +import { defineComponent, PropType, ref, computed, watch } from 'vue'; |
2 | 2 | import { ConfigDto, IEntryState, ISectionState, Section } from "@/client/apiGen"; |
3 | | -import { NAnchor, NAnchorLink } from "naive-ui"; |
4 | | -import { CheckBox, Popover, TextInput, Button } from '@munet/ui'; |
| 3 | +import { CheckBox, Popover, TextInput, Button, theme } from '@munet/ui'; |
5 | 4 | import _ from "lodash"; |
6 | 5 | import ProblemsDisplay from "@/components/ProblemsDisplay"; |
7 | 6 | import configSortStub from './configSort.yaml' |
@@ -35,12 +34,12 @@ const ConfigSection = defineComponent({ |
35 | 34 | return props.section.attribute?.comment?.commentEn; |
36 | 35 | }) |
37 | 36 |
|
38 | | - return () => <div class="flex flex-col gap-2 p-1 border-transparent border-solid border-1px rd hover:border-yellow-5"> |
| 37 | + return () => <div class="flex flex-col gap-2 p-1 border-transparent border-solid border-1px rd hover:border-[oklch(0.68_0.17_var(--hue))]"> |
39 | 38 | {!props.section.attribute!.alwaysEnabled && <div class="flex gap-2 items-start" |
40 | 39 | // @ts-ignore |
41 | 40 | title={props.section.path!} |
42 | 41 | > |
43 | | - <div class="ml-1 text-sm w-9em shrink-0">{getNameForPath(props.section.path!, props.section.path!.split('.').pop()!, props.section.attribute?.comment?.nameZh)}</div> |
| 42 | + <div class="ml-1 text-lg w-9em shrink-0">{getNameForPath(props.section.path!, props.section.path!.split('.').pop()!, props.section.attribute?.comment?.nameZh)}</div> |
44 | 43 | <div class="flex flex-col gap-2 w-full ws-pre-line"> |
45 | 44 | <div class="flex gap-2 h-34px items-center"> |
46 | 45 | <CheckBox v-model:value={props.sectionState.enabled}>{props.sectionState.enabled ? '开' : '关'}</CheckBox> |
@@ -78,6 +77,7 @@ export default defineComponent({ |
78 | 77 | setup(props, { emit }) { |
79 | 78 | const search = ref(''); |
80 | 79 | const searchRef = ref(); |
| 80 | + const activeTab = ref<string | null>(null); |
81 | 81 | const configSort = computed(() => props.config?.configSort || configSortStub) |
82 | 82 | const communityList = computed(() => configSort.value['社区功能'] || []); |
83 | 83 | const { t } = useI18n(); |
@@ -118,64 +118,90 @@ export default defineComponent({ |
118 | 118 | return filteredSections.value?.filter(it => !knownSections.includes(it.path!) && !it.attribute!.exampleHidden) || []; |
119 | 119 | }); |
120 | 120 |
|
| 121 | + // 所有可选 tab,包括 "其他" |
| 122 | + const allTabs = computed(() => { |
| 123 | + const tabs = bigSections.value.map(key => ({ key: key!, label: getBigSectionName(key!) })); |
| 124 | + if (otherSection.value.length > 0) { |
| 125 | + tabs.push({ key: '__other__', label: t('mod.other') }); |
| 126 | + } |
| 127 | + return tabs; |
| 128 | + }); |
| 129 | + |
| 130 | + // 默认选中第一个 tab |
| 131 | + watch(() => allTabs.value, (tabs) => { |
| 132 | + if (tabs.length > 0 && (!activeTab.value || !tabs.some(t => t.key === activeTab.value))) { |
| 133 | + activeTab.value = tabs[0].key; |
| 134 | + } |
| 135 | + }, { immediate: true }); |
| 136 | + |
| 137 | + // 当前要显示的 sections:搜索时显示所有匹配结果,否则只显示当前 tab |
| 138 | + const currentSections = computed(() => { |
| 139 | + if (search.value) { |
| 140 | + // 搜索模式:显示所有匹配的 sections(不限 tab) |
| 141 | + return filteredSections.value?.filter(it => !it.attribute?.exampleHidden) || []; |
| 142 | + } |
| 143 | + if (!activeTab.value) return []; |
| 144 | + if (activeTab.value === '__other__') return otherSection.value; |
| 145 | + return filteredSections.value?.filter(it => { |
| 146 | + if (props.useNewSort) { |
| 147 | + return configSort.value[activeTab.value!]?.includes(it.path!); |
| 148 | + } |
| 149 | + return it.path!.split('.')[0] === activeTab.value && !it.attribute!.exampleHidden; |
| 150 | + }).sort((a, b) => { |
| 151 | + if (!props.useNewSort) return 0; |
| 152 | + return configSort.value[activeTab.value!].indexOf(a.path!) - configSort.value[activeTab.value!].indexOf(b.path!); |
| 153 | + }) || []; |
| 154 | + }); |
| 155 | + |
121 | 156 | return () => <div class="grid cols-[15em_auto] max-[900px]:cols-1"> |
122 | | - <NAnchor type="block" offsetTarget="#scroll" class={["max-[900px]:hidden"]}> |
123 | | - {bigSections.value.map((key) => <NAnchorLink key={key} title={getBigSectionName(key!)} href={`#${key}`}/>)} |
124 | | - {otherSection.value.length > 0 && <NAnchorLink key={t('mod.other')} title={t('mod.other')} href={`#${t('mod.other')}`}/>} |
125 | | - </NAnchor> |
126 | | - <div class="of-y-auto cst h-[calc(100dvh-160px)] p-2 relative text-14px" |
127 | | - // @ts-ignore |
128 | | - id="scroll" |
129 | | - > |
130 | | - <div class={'absolute top-1 left-4 max-[900px]:left-0 right-4 z-200 flex gap-2'}> |
| 157 | + {/* 左侧导航 */} |
| 158 | + <div class="flex flex-col gap-0.5 max-[900px]:hidden of-y-auto h-[calc(100dvh-80px)]"> |
| 159 | + {allTabs.value.map(tab => |
| 160 | + <div |
| 161 | + key={tab.key} |
| 162 | + class={[ |
| 163 | + 'px-3 py-1.5 rd cursor-pointer text-sm transition-colors', |
| 164 | + activeTab.value === tab.key && theme.value.listItemSelect, theme.value.listItemHover, |
| 165 | + ]} |
| 166 | + onClick={() => activeTab.value = tab.key} |
| 167 | + > |
| 168 | + {tab.label} |
| 169 | + </div> |
| 170 | + )} |
| 171 | + </div> |
| 172 | + <div class="flex flex-col h-[calc(100dvh-80px)]"> |
| 173 | + <div class="flex gap-2 p-2 shrink-0"> |
131 | 174 | <div class={["min-[900px]:hidden"]}> |
132 | 175 | <Popover trigger="click">{{ |
133 | 176 | trigger: () => <Button variant="secondary" size="small"><span class="i-ic-baseline-menu text-lg"/></Button>, |
134 | | - default: () => <NAnchor type="block" offsetTarget="#scroll"> |
135 | | - {bigSections.value.map((key) => <NAnchorLink key={key} title={getBigSectionName(key!)} href={`#${key}`}/>)} |
136 | | - {otherSection.value.length > 0 && <NAnchorLink key={t('mod.other')} title={t('mod.other')} href={`#${t('mod.other')}`}/>} |
137 | | - </NAnchor> |
| 177 | + default: () => <div class="flex flex-col gap-0.5"> |
| 178 | + {allTabs.value.map(tab => |
| 179 | + <div |
| 180 | + key={tab.key} |
| 181 | + class={[ |
| 182 | + 'px-3 py-1.5 rd cursor-pointer text-sm', |
| 183 | + activeTab.value === tab.key && theme.value.listItemSelect, theme.value.listItemHover, |
| 184 | + ]} |
| 185 | + onClick={() => activeTab.value = tab.key} |
| 186 | + > |
| 187 | + {tab.label} |
| 188 | + </div> |
| 189 | + )} |
| 190 | + </div> |
138 | 191 | }}</Popover> |
139 | 192 | </div> |
140 | 193 | {/* @ts-ignore */} |
141 | | - <TextInput v-model:value={search.value} placeholder={t('mod.searchPlaceholder')} ref={searchRef}/> |
| 194 | + <TextInput v-model:value={search.value} placeholder={t('mod.searchPlaceholder')} ref={searchRef} class="flex-1"/> |
142 | 195 | </div> |
143 | | - {bigSections.value.map((big) => <div id={big} key={big}> |
144 | | - <div class={["mt-0! pt-8 sticky top-0! z-1 bg-modal flex items-center gap-2 cursor-pointer"]} |
145 | | - onClick={() => location.href = `#${big}`} |
146 | | - > |
147 | | - <hr class="border-white/10 flex-1"/> |
148 | | - <span>{getBigSectionName(big!)}</span> |
149 | | - <hr class="border-white/10 flex-1"/> |
150 | | - </div> |
151 | | - {filteredSections.value?.filter(it => { |
152 | | - if (props.useNewSort) { |
153 | | - return configSort.value[big!].includes(it.path!); |
154 | | - } |
155 | | - return it.path!.split('.')[0] === big && !it.attribute!.exampleHidden; |
156 | | - }).sort((a, b) => { |
157 | | - if (!props.useNewSort) return 0; |
158 | | - return configSort.value[big!].indexOf(a.path!) - configSort.value[big!].indexOf(b.path!); |
159 | | - }).map((section) => { |
160 | | - return <ConfigSection key={section.path!} section={section} |
161 | | - entryStates={props.config.entryStates!} |
162 | | - isCommunity={communityList.value.includes(section.path!)} |
163 | | - sectionState={props.config.sectionStates![section.path!]}/>; |
164 | | - })} |
165 | | - </div>)} |
166 | | - {otherSection.value.length > 0 && |
167 | | - <div id={t('mod.other')}> |
168 | | - <div class="mt-2! flex items-center gap-2"> |
169 | | - <hr class="border-white/10 flex-1"/> |
170 | | - <span>{t('mod.other')}</span> |
171 | | - <hr class="border-white/10 flex-1"/> |
172 | | - </div> |
173 | | - {otherSection.value.map((section) => |
| 196 | + <div class="of-y-auto cst flex-1 p-2 pt-0 text-14px"> |
| 197 | + <div class="flex flex-col gap-1"> |
| 198 | + {currentSections.value.map((section) => |
174 | 199 | <ConfigSection key={section.path!} section={section} |
175 | 200 | entryStates={props.config.entryStates!} |
176 | 201 | isCommunity={communityList.value.includes(section.path!)} |
177 | 202 | sectionState={props.config.sectionStates![section.path!]}/>)} |
178 | | - </div>} |
| 203 | + </div> |
| 204 | + </div> |
179 | 205 | </div> |
180 | 206 | </div>; |
181 | 207 | }, |
|
0 commit comments