mirror of https://github.com/mashirozx/sakura.git
219 lines
5.8 KiB
Vue
219 lines
5.8 KiB
Vue
<template>
|
|
<div class="layout mdc-card mdc-card--outlined">
|
|
<div class="tab-bar__wrapper">
|
|
<TabBar v-model:current="currentTabIndex" :items="tabs"></TabBar>
|
|
</div>
|
|
<Swiper
|
|
class="tab-page__wrapper"
|
|
:slidesPerView="1"
|
|
:spaceBetween="50"
|
|
:allowTouchMove="false"
|
|
:autoHeight="true"
|
|
@swiper="handleSwiperEvent"
|
|
>
|
|
<SwiperSlide
|
|
class="tab-page__container"
|
|
v-for="(tabKey, tabKeyIndex) in tabKeys"
|
|
:key="tabKeyIndex"
|
|
>
|
|
<div class="tab-page__content">
|
|
<h1 class="row__wrapper--title">{{ options[tabKey].title }}</h1>
|
|
<p class="row__wrapper--desc" v-if="options[tabKey].desc" v-html="options[tabKey].desc">
|
|
</p>
|
|
<transition-group name="row__wrapper--options">
|
|
<div
|
|
class="option__wrapper"
|
|
v-for="(option, optionIndex) in options[tabKey].options"
|
|
:key="optionIndex"
|
|
v-show="shouldOptionShow(option)"
|
|
>
|
|
<OptionItem :option="option"></OptionItem>
|
|
</div>
|
|
</transition-group>
|
|
</div>
|
|
</SwiperSlide>
|
|
</Swiper>
|
|
<div class="buttons__wrapper">
|
|
<NormalButton
|
|
:icon="['fas', saving ? 'fa-spinner fa-spin' : 'fa-save'].join(' ')"
|
|
context="Save"
|
|
:contained="true"
|
|
@click="handleSaveEvent"
|
|
></NormalButton>
|
|
<NormalButton icon="fas fa-upload" context="Import" :contained="true"></NormalButton>
|
|
<NormalButton icon="fas fa-download" context="Export" :contained="true"></NormalButton>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { defineComponent, ref, Ref, watch } from 'vue'
|
|
import { Swiper, SwiperSlide } from 'swiper/vue'
|
|
import type { Swiper as SwiperInterface } from 'swiper'
|
|
import 'swiper/swiper.scss'
|
|
import { useInjector, useState, useMessage, useIntervalWatcher } from '@/hooks'
|
|
import store from './store'
|
|
import options from './options'
|
|
import type { Option } from './options'
|
|
import API from './api'
|
|
import TabBar from '@/components/tabBar/TabBar.vue'
|
|
import OptionItem from './OptionItem.vue'
|
|
import NormalButton from '@/components/buttons/NormalButton.vue'
|
|
|
|
export default defineComponent({
|
|
components: { TabBar, Swiper, SwiperSlide, OptionItem, NormalButton },
|
|
setup() {
|
|
// UI controllers
|
|
const tabKeys = Object.keys(options)
|
|
const tabs = tabKeys.map((key) => {
|
|
return { context: options[key].title, icon: options[key].icon, key }
|
|
})
|
|
|
|
let defaultCurrentTabIndex = 0
|
|
if (window.location.hash) {
|
|
const locationHashMatch = window.location.hash.match(/^#(.*)/)
|
|
if (locationHashMatch && locationHashMatch[1] && tabKeys.indexOf(locationHashMatch[1]) > -1) {
|
|
defaultCurrentTabIndex = tabKeys.indexOf(locationHashMatch[1])
|
|
}
|
|
}
|
|
const currentTabIndex: Ref<number> = ref(defaultCurrentTabIndex)
|
|
const swiperRef: Ref<SwiperInterface | null> = ref(null)
|
|
|
|
const handleSwiperEvent = (swiper: SwiperInterface) => {
|
|
swiperRef.value = swiper
|
|
swiper.slideTo(currentTabIndex.value)
|
|
}
|
|
|
|
watch(currentTabIndex, (current) => {
|
|
swiperRef.value?.slideTo(current)
|
|
window.location.hash = `#${tabs[current].key}`
|
|
})
|
|
|
|
const updateAutoHeight = (timeout = 0) => swiperRef.value?.updateAutoHeight(timeout)
|
|
|
|
useIntervalWatcher(() => updateAutoHeight(100), 100)
|
|
|
|
// messages
|
|
const addMessage = useMessage()
|
|
|
|
// data controllers
|
|
const [saving, setSaving] = useState(false)
|
|
const { config } = useInjector(store)
|
|
|
|
const handleSaveEvent = () => {
|
|
if (saving.value) return
|
|
setSaving(true)
|
|
API.postConfigJson(config.value)
|
|
.then((res) => {
|
|
setSaving(false)
|
|
addMessage({
|
|
title: res.data.message,
|
|
type: 'success',
|
|
})
|
|
})
|
|
.catch((error) => {
|
|
setSaving(false)
|
|
console.error(error)
|
|
addMessage({
|
|
title: error.toString(),
|
|
type: 'error',
|
|
})
|
|
})
|
|
}
|
|
|
|
const shouldOptionShow = (option: Option) => {
|
|
if (option.depends) {
|
|
return option.depends(config)
|
|
} else {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return {
|
|
currentTabIndex,
|
|
tabKeys,
|
|
tabs,
|
|
options,
|
|
handleSwiperEvent,
|
|
handleSaveEvent,
|
|
shouldOptionShow,
|
|
saving,
|
|
}
|
|
},
|
|
})
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
@use '@/styles/mixins/polyfills';
|
|
|
|
// ::v-deep() {
|
|
// @import 'swiper/swiper';
|
|
// }
|
|
|
|
.layout {
|
|
width: 100%;
|
|
overflow: hidden;
|
|
> .tab-bar__wrapper {
|
|
width: 100%;
|
|
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
|
}
|
|
> .tab-page__wrapper {
|
|
width: 100%;
|
|
.tab-page__container {
|
|
width: 100%;
|
|
.tab-page__content {
|
|
width: calc(100% - 24px);
|
|
padding: 12px;
|
|
.row__wrapper {
|
|
&--options {
|
|
&-enter-active {
|
|
transform-origin: top;
|
|
animation: from 0.3s forwards;
|
|
}
|
|
&-leave-active {
|
|
transform-origin: top;
|
|
animation: to 0.3s forwards;
|
|
}
|
|
&-move {
|
|
transition: transform 0.3s ease;
|
|
}
|
|
}
|
|
}
|
|
@keyframes from {
|
|
0% {
|
|
transform: scaleY(0);
|
|
opacity: 0;
|
|
}
|
|
100% {
|
|
transform: scaleY(1);
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
@keyframes to {
|
|
0% {
|
|
height: 56px;
|
|
transform: scaleY(1);
|
|
opacity: 1;
|
|
}
|
|
100% {
|
|
height: 0;
|
|
transform: scaleY(0);
|
|
opacity: 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
> .buttons__wrapper {
|
|
display: flex;
|
|
flex-flow: row wrap;
|
|
justify-content: flex-start;
|
|
align-items: center;
|
|
padding: 0 12px 12px 12px;
|
|
width: calc(100% - 24px);
|
|
@include polyfills.flex-gap(12px, 'row wrap');
|
|
}
|
|
}
|
|
</style>
|