feat: 移动端响应式布局

This commit is contained in:
Rewrite0
2024-05-29 15:12:46 +08:00
parent 104a6c5332
commit ae43989bc8
26 changed files with 2066 additions and 618 deletions

View File

@@ -45,7 +45,7 @@
"@unocss/preset-attributify": "^0.55.3",
"@unocss/preset-rem-to-px": "^0.51.13",
"@unocss/reset": "^0.51.13",
"@vitejs/plugin-vue": "^4.2.0",
"@vitejs/plugin-vue": "^4.2.3",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"@vue/runtime-dom": "^3.3.4",
"eslint": "^8.41.0",

2265
webui/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -35,5 +35,6 @@ if (isLoggedIn.value) {
<style lang="scss">
@import './style/transition';
@import './style/var';
@import './style/global';
</style>

View File

@@ -16,17 +16,17 @@ withDefaults(
w-full
text-white
px-20
h-45
h="38 pc:45"
fx-cer
justify-between
select-none
>
<div text-h2>{{ title }}</div>
<div text="h3 pc:h2">{{ title }}</div>
<slot name="title-right"></slot>
</div>
<div p-20 bg-white>
<div p="14 pc:20" bg-white text="14 inherit">
<slot></slot>
</div>
</div>

View File

@@ -75,7 +75,11 @@ const boxSize = computed(() => {
</script>
<template>
<ab-popup v-model:show="show" :title="popupTitle" :css="boxSize">
<ab-popup
v-model:show="show"
:title="popupTitle"
:css="`${boxSize} max-w-90vw`"
>
<div v-if="rule.deleted">
<div>{{ $t('homepage.rule.enable_hit') }}</div>

View File

@@ -21,15 +21,20 @@ withDefaults(
text-white
fx-cer
px-20
h-45
h="38 pc:45"
justify-between
>
<div text-h2>{{ title }}</div>
<div text="h3 pc:h2">{{ title }}</div>
<Component :is="open ? Up : Down" size="24" />
</DisclosureButton>
<div bg-white py-20 :class="[open ? 'px-20' : 'px-8']">
<div
bg-white
py="10 pc:20"
text="14 inherit"
:class="[open ? 'px-20' : 'px-8']"
>
<div v-show="!open" line my-12></div>
<DisclosurePanel>

View File

@@ -34,10 +34,10 @@ function abLabel(label: string | (() => string)) {
<template>
<Menu>
<div rel>
<div fx-cer space-x-16>
<div fx-cer space-x="pc:16 10" text="pc:24 20">
<International
theme="outline"
size="24"
size="1em"
fill="#fff"
is-btn
btn-click
@@ -46,7 +46,7 @@ function abLabel(label: string | (() => string)) {
<AddOne
theme="outline"
size="24"
size="1em"
fill="#fff"
is-btn
btn-click
@@ -54,7 +54,7 @@ function abLabel(label: string | (() => string)) {
/>
<MenuButton bg-transparent is-btn btn-click>
<System theme="outline" size="24" fill="#fff" />
<System theme="outline" size="1em" fill="#fff" />
</MenuButton>
<ab-status :running="running" />
@@ -62,8 +62,8 @@ function abLabel(label: string | (() => string)) {
<MenuItems
abs
top-50
left-32
top="pc:50 40"
left="pc:32 0"
w-120
rounded-8
bg-white

View File

@@ -11,7 +11,7 @@ withDefaults(
<template>
<div fx-cer gap-x-12>
<div text-h1>{{ title }}</div>
<div text="pc:h1 h2">{{ title }}</div>
<div w-160 h-3 bg-theme-row rounded-full></div>
</div>
</template>

View File

@@ -2,16 +2,18 @@
withDefaults(
defineProps<{
running: boolean;
size?: string;
}>(),
{
running: false,
size: '1em',
}
);
</script>
<template>
<div wh-24 f-cer>
<div rounded="1/2" f-cer border="2 solid white" wh-22>
<div f-cer :style="{ width: size, height: size }">
<div rounded="1/2" f-cer border="2 solid white" wh-full>
<div
:class="[running ? 'bg-running' : 'bg-stopped']"
rounded="1/2"

View File

@@ -1,4 +1,4 @@
<script lang="ts" setup>
<script lang="tsx" setup>
import {
Calendar,
Download,
@@ -23,6 +23,7 @@ const props = withDefaults(
const { t } = useMyI18n();
const { logout } = useAuth();
const route = useRoute();
const { isMobile } = useBreakpointQuery();
const show = ref(props.open);
const toggle = () => (show.value = !show.value);
@@ -79,81 +80,122 @@ const items = [
path: '/config',
},
];
</script>
<template>
<div
:class="[show ? 'w-240' : 'w-72']"
bg-theme-col
text-white
transition-width
pb-12
rounded-16
>
<div overflow-hidden wh-full flex="~ col">
<div
w-full
h-60
is-btn
f-cer
rounded-t-10
bg="#E7E7E7"
text="#2A1C52"
rel
@click="toggle"
>
<div :class="[!show && 'abs opacity-0']" transition-opacity>
<div text-h1>{{ $t('sidebar.title') }}</div>
</div>
<MenuUnfold
theme="outline"
size="24"
fill="#2A1C52"
abs
left-24
:class="[show && 'rotate-y-180']"
/>
</div>
<RouterLink
v-for="i in items"
:key="i.id"
:to="i.path"
replace
:title="i.label()"
fx-cer
px-24
gap-x-42
h-48
is-btn
transition-colors
hover="bg-[#F1F5FA] text-[#2A1C52]"
:class="[
route.path === i.path && 'bg-[#F1F5FA] text-[#2A1C52]',
i.hidden && 'hidden',
]"
>
<Component :is="i.icon" :size="24" />
<div text-h2 whitespace-nowrap>{{ i.label() }}</div>
</RouterLink>
<div
title="logout"
function Exit() {
return (
<div
title="logout"
class={[
`
mt-auto
fx-cer
gap-x-42
px-24
h-48
is-btn
transition-colors
hover="bg-[#F1F5FA] text-[#2A1C52]"
@click="logout"
>
<Logout :size="24" />
<div text-h2>{{ $t('sidebar.logout') }}</div>
`,
isMobile.value ? 'h-40' : 'h-48',
]}
hover="bg-[#F1F5FA] text-[#2A1C52]"
onClick={logout}
>
<Logout size={24} />
{!isMobile.value && <div class="text-h2">{t('sidebar.logout')}</div>}
</div>
);
}
const mobileItems = computed(() => items.filter((i) => i.id !== 4));
</script>
<template>
<media-query>
<div
:class="[show ? 'w-240' : 'w-72']"
bg-theme-col
text-white
transition-width
pb-12
rounded="pc:16 10"
>
<div overflow-hidden wh-full flex="~ col">
<div
w-full
h-60
is-btn
f-cer
rounded-t-10
bg="#E7E7E7"
text="#2A1C52"
rel
@click="toggle"
>
<div :class="[!show && 'abs opacity-0']" transition-opacity>
<div text-h1>{{ $t('sidebar.title') }}</div>
</div>
<MenuUnfold
theme="outline"
size="24"
fill="#2A1C52"
abs
left-24
:class="[show && 'rotate-y-180']"
/>
</div>
<RouterLink
v-for="i in items"
:key="i.id"
:to="i.path"
replace
:title="i.label()"
fx-cer
px-24
gap-x-42
h-48
is-btn
transition-colors
hover="bg-[#F1F5FA] text-[#2A1C52]"
:class="[
route.path === i.path && 'bg-[#F1F5FA] text-[#2A1C52]',
i.hidden && 'hidden',
]"
>
<Component :is="i.icon" :size="24" />
<div text-h2 whitespace-nowrap>{{ i.label() }}</div>
</RouterLink>
<Exit />
</div>
</div>
</div>
<template #mobile>
<div bg-white flex rounded-10 overflow-hidden>
<RouterLink
v-for="i in mobileItems"
:key="i.id"
:to="i.path"
replace
flex-1
fx-cer
px-24
gap-x-42
h-40
is-btn
transition-colors
rounded-10
:class="[
route.path === i.path && 'bg-theme-row text-white',
i.hidden && 'hidden',
]"
>
<Component :is="i.icon" :size="24" />
</RouterLink>
<Exit />
</div>
</template>
</media-query>
</template>

View File

@@ -94,21 +94,30 @@ onUnmounted(() => {
</script>
<template>
<div h-60 bg-theme-row text-white rounded-16 fx-cer px-24>
<div
h="pc:60 50"
bg-theme-row
text-white
rounded="pc:16 10"
fx-cer
px="pc:24 15"
>
<div flex="~ gap-x-16">
<div fx-cer gap-x-16>
<img src="/images/logo-light.svg" alt="favicon" wh-24 />
<div fx-cer gap-x="pc:16 10">
<img src="/images/logo-light.svg" alt="favicon" wh="pc:24 20" />
<img
v-show="onSearchFocus === false"
src="/images/AutoBangumi.svg"
alt="AutoBangumi"
h-24
rel
top-2
h="18 pc:24"
pc:top-2
/>
</div>
<ab-search-bar @add-bangumi="addSearchResult" />
<div hidden pc:block>
<ab-search-bar @add-bangumi="addSearchResult" />
</div>
</div>
<div ml-auto>

View File

@@ -0,0 +1,15 @@
<script lang="ts" setup>
const { isMobile } = useBreakpointQuery();
</script>
<template>
<template v-if="isMobile">
<slot name="mobile"></slot>
</template>
<template v-else>
<slot></slot>
</template>
</template>
<style lang="scss" scope></style>

View File

@@ -0,0 +1,16 @@
import { createSharedComposable, useBreakpoints } from '@vueuse/core';
export const useBreakpointQuery = createSharedComposable(() => {
const breakpoints = useBreakpoints({
pc: 1024,
});
const isMobile = breakpoints.smaller('pc');
const isPC = breakpoints.isGreater('pc');
return {
breakpoints,
isMobile,
isPC,
};
});

View File

@@ -29,15 +29,24 @@ definePage({
.layout-container {
width: 100%;
height: 100%;
min-width: 1024px;
min-height: 768px;
padding: 16px;
padding: var(--layout-padding);
gap: var(--layout-gap);
display: flex;
flex-direction: column;
gap: 12px;
background: #f0f0f0;
@include forPC {
min-width: 1024px;
min-height: 768px;
}
@include forMobile {
overflow: hidden;
height: 100vh;
}
}
.layout-main {
@@ -45,7 +54,13 @@ definePage({
gap: 20px;
overflow: hidden;
height: calc(100vh - 2 * 16px - 60px - 12px);
height: calc(100vh - 2 * var(--layout-padding) - 60px - var(--layout-gap));
@include forMobile {
flex-direction: column-reverse;
height: calc(100vh - var(--layout-padding) * 2 - var(--layout-gap));
gap: var(--layout-gap);
}
}
.layout-content {

View File

@@ -7,6 +7,8 @@ const { bangumi, editRule } = storeToRefs(useBangumiStore());
const { getAll, updateRule, enableRule, openEditPopup, ruleManage } =
useBangumiStore();
const { isMobile } = useBreakpointQuery();
onActivated(() => {
getAll();
});
@@ -15,7 +17,13 @@ onActivated(() => {
<template>
<div overflow-auto mt-12 flex-grow>
<div>
<transition-group name="bangumi" tag="div" flex="~ wrap" gap="x-32 y-12">
<transition-group
name="bangumi"
tag="div"
flex="~ wrap"
gap="20"
:class="{ 'justify-center': isMobile }"
>
<ab-bangumi-card
v-for="i in bangumi"
:key="i.id"

View File

@@ -4,6 +4,7 @@ definePage({
});
const { getConfig, setConfig } = useConfigStore();
const { isMobile } = useBreakpointQuery();
onActivated(() => {
getConfig();
@@ -12,8 +13,8 @@ onActivated(() => {
<template>
<div overflow-auto mt-12 flex-grow>
<div h-full flex="~ col">
<div grid="~ cols-2 gap-20" mb-auto>
<div h-full>
<div grid="~ pc:cols-2 gap-20" mb-auto>
<div space-y-20>
<config-normal></config-normal>
@@ -36,10 +37,20 @@ onActivated(() => {
</div>
<div fx-cer justify-end gap-8 mt-20>
<ab-button type="warn" @click="getConfig">
<ab-button
:class="[{ 'flex-1': isMobile }]"
type="warn"
@click="getConfig"
>
{{ $t('config.cancel') }}
</ab-button>
<ab-button @click="setConfig">{{ $t('config.apply') }}</ab-button>
<ab-button
:class="[{ 'flex-1': isMobile }]"
type="primary"
@click="setConfig"
>
{{ $t('config.apply') }}
</ab-button>
</div>
</div>
</div>

View File

@@ -76,22 +76,27 @@ onDeactivated(() => {
overflow-auto
p-10
max-h-60vh
min-h-20vh
>
<template v-for="i in formatLog" :key="i.index">
<div
p="y-10"
leading="1.5em"
border="0 b-1 solid"
last:border-b-0
flex="~ gap-10"
:style="{ color: typeColor(i.type) }"
>
<span>{{ i.date }}</span>
<span w-6em text="center">{{ i.type }}</span>
<div flex-1 break-all>{{ i.content }}</div>
</div>
</template>
<!-- <pre text-main>{{ log }}</pre> -->
<div min-w-450>
<template v-for="i in formatLog" :key="i.index">
<div
p="y-10"
leading="1.5em"
border="0 b-1 solid"
last:border-b-0
flex="~ items-center gap-20"
:style="{ color: typeColor(i.type) }"
>
<div flex="~ col items-center gap-10" whitespace-nowrap>
<div text="center">{{ i.type }}</div>
<div>{{ i.date }}</div>
</div>
<div flex-1 break-all>{{ i.content }}</div>
</div>
</template>
</div>
</div>
<div flex="~ justify-end gap-x-10" mt-12>

View File

@@ -32,7 +32,7 @@ const RSSTableOptions = computed(() => {
title: t('rss.url'),
key: 'url',
className: 'text-h3',
minWidth: 200,
minWidth: 400,
align: 'center',
ellipsis: {
tooltip: true,

View File

@@ -8,7 +8,7 @@ const { user, login } = useAuth();
<template>
<div wh-screen f-cer bg-page>
<ab-container :title="$t('login.title')" w-365>
<ab-container :title="$t('login.title')" w-365 max-w="90%">
<div space-y-16>
<ab-label :label="$t('login.username')">
<input

View File

@@ -1,12 +1,3 @@
$scrollbar-color: #372a87;
:root {
--scrollbar-size: 6px;
--scrollbar-color: transparent;
--scrollbar-thumb-color: #{rgba($scrollbar-color, 0.5)};
--scrollbar-thumb-hover-color: #{rgba($scrollbar-color, 1)};
}
::-webkit-scrollbar {
width: var(--scrollbar-size);
height: var(--scrollbar-size);

View File

@@ -1,3 +1,17 @@
$min-pc: 1024px;
@mixin forMobile {
@media screen and (max-width: ($min-pc - 1)) {
@content;
}
}
@mixin forPC {
@media screen and (min-width: $min-pc) {
@content;
}
}
@mixin bg-mouse-event($normal, $hover, $active) {
background: $normal;
transition: background 0.3s;

16
webui/src/style/var.scss Normal file
View File

@@ -0,0 +1,16 @@
$scrollbar-color: #372a87;
:root {
--scrollbar-size: 6px;
--scrollbar-color: transparent;
--scrollbar-thumb-color: #{rgba($scrollbar-color, 0.5)};
--scrollbar-thumb-hover-color: #{rgba($scrollbar-color, 1)};
--layout-padding: 16px;
--layout-gap: 12px;
@include forMobile {
--layout-padding: 10px;
--layout-gap: 10px;
}
}

View File

@@ -146,6 +146,7 @@ declare global {
const useBase64: typeof import('@vueuse/core')['useBase64']
const useBattery: typeof import('@vueuse/core')['useBattery']
const useBluetooth: typeof import('@vueuse/core')['useBluetooth']
const useBreakpointQuery: typeof import('../../src/hooks/useBreakpointQuery')['useBreakpointQuery']
const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints']
const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel']
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']

View File

@@ -41,6 +41,7 @@ declare module '@vue/runtime-core' {
ConfigParser: typeof import('./../../src/components/setting/config-parser.vue')['default']
ConfigPlayer: typeof import('./../../src/components/setting/config-player.vue')['default']
ConfigProxy: typeof import('./../../src/components/setting/config-proxy.vue')['default']
MediaQuery: typeof import('./../../src/components/media-query.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}

View File

@@ -28,6 +28,9 @@ export default defineConfig({
},
],
theme: {
breakpoints: {
pc: '1024px',
},
colors: {
primary: '#493475',
running: '#A3D491',

View File

@@ -107,8 +107,8 @@ export default defineConfig(({ mode }) => ({
},
server: {
proxy: {
'^/api/.*': 'http://127.0.0.1:7892',
'^/posters/.*': 'http://127.0.0.1:7892',
'^/api/.*': 'http://192.168.0.100:7892',
'^/posters/.*': 'http://192.168.0.100:7892',
},
},
}));