init project
This commit is contained in:
6
src/pages/detail/FileDetailPage.vue
Normal file
6
src/pages/detail/FileDetailPage.vue
Normal file
@@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<route-placeholder title="文件详情页" description="这里保留为二级详情占位,用于验证从文件页进入和返回时的滑动方向。" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
6
src/pages/detail/ServerLogPage.vue
Normal file
6
src/pages/detail/ServerLogPage.vue
Normal file
@@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<route-placeholder title="服务日志页" description="这里保留为二级详情占位,用于验证从状态页进入日志页时的动效和返回行为。" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
21
src/pages/system/LoginPage.vue
Normal file
21
src/pages/system/LoginPage.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<route-placeholder title="LoginPage" description="登录页占位,当前仅保留独立路由入口,不引入业务表单。" />
|
||||
<t-button block theme="primary" @click="router.replace('/')">
|
||||
进入应用首页
|
||||
</t-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const router = useRouter();
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.page {
|
||||
gap: 1rem;
|
||||
display: flex;
|
||||
padding: 1.2rem;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
6
src/pages/system/NotFoundPage.vue
Normal file
6
src/pages/system/NotFoundPage.vue
Normal file
@@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<route-placeholder title="NotFoundPage" description="未匹配到路由时显示的占位页,避免空白页面。" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
202
src/pages/system/ServerIndexPage.vue
Normal file
202
src/pages/system/ServerIndexPage.vue
Normal file
@@ -0,0 +1,202 @@
|
||||
<template>
|
||||
<div class="server-index">
|
||||
<section class="panel">
|
||||
<div class="hero">
|
||||
<p class="tag">NAS 连接</p>
|
||||
<h1 class="title">先配置服务器连接</h1>
|
||||
<p class="desc">缺少连接配置时,应用不会进入文件、状态和设置页面。</p>
|
||||
</div>
|
||||
|
||||
<div class="form-card">
|
||||
<div class="group">
|
||||
<p class="label">协议</p>
|
||||
<div class="protocols">
|
||||
<t-button
|
||||
size="small"
|
||||
theme="primary"
|
||||
:variant="form.protocol === 'http' ? 'base' : 'outline'"
|
||||
@click="setProtocol('http')"
|
||||
>
|
||||
HTTP
|
||||
</t-button>
|
||||
<t-button
|
||||
size="small"
|
||||
theme="primary"
|
||||
:variant="form.protocol === 'https' ? 'base' : 'outline'"
|
||||
@click="setProtocol('https')"
|
||||
>
|
||||
HTTPS
|
||||
</t-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="group">
|
||||
<p class="label">主机地址</p>
|
||||
<t-input v-model="form.host" clearable placeholder="例如 192.168.1.100 或 nas.local" />
|
||||
</div>
|
||||
|
||||
<div class="group">
|
||||
<p class="label">端口</p>
|
||||
<t-input v-model="form.port" clearable type="number" placeholder="例如 8080" />
|
||||
</div>
|
||||
|
||||
<div class="group">
|
||||
<p class="label">访问令牌</p>
|
||||
<t-input v-model="form.token" clearable placeholder="请输入 token" />
|
||||
</div>
|
||||
|
||||
<t-button block theme="primary" size="large" @click="submitConnect">
|
||||
保存并进入
|
||||
</t-button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Toast } from "tdesign-mobile-vue";
|
||||
import type { ConnectProtocol, ConnectSetting } from "@/store/settingStore";
|
||||
import { useSettingStore } from "@/store/settingStore";
|
||||
|
||||
defineOptions({
|
||||
name: "ServerIndexPage"
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const settingStore = useSettingStore();
|
||||
|
||||
const form = reactive<ConnectSetting>({
|
||||
protocol: "http",
|
||||
host: "",
|
||||
port: "",
|
||||
token: ""
|
||||
});
|
||||
|
||||
watch(
|
||||
() => settingStore.connect,
|
||||
(connect) => {
|
||||
Object.assign(form, connect);
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
function setProtocol(protocol: ConnectProtocol): void {
|
||||
form.protocol = protocol;
|
||||
}
|
||||
|
||||
function validateConnect(): boolean {
|
||||
const host = form.host.trim();
|
||||
const port = form.port.trim();
|
||||
const token = form.token.trim();
|
||||
|
||||
if (!host || !port || !token) {
|
||||
Toast({
|
||||
theme: "warning",
|
||||
message: "请完整填写连接配置"
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async function submitConnect(): Promise<void> {
|
||||
if (!validateConnect()) {
|
||||
return;
|
||||
}
|
||||
|
||||
settingStore.setConnect(form);
|
||||
|
||||
const redirect = typeof route.query.redirect === "string" ? route.query.redirect : "/";
|
||||
await router.replace(redirect);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.server-index {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
|
||||
.panel {
|
||||
gap: 1.2rem;
|
||||
display: flex;
|
||||
padding: 1.2rem;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
background:
|
||||
radial-gradient(circle at top right, rgba(0, 82, 217, .18), transparent 36%),
|
||||
linear-gradient(180deg, rgba(255, 255, 255, .92), rgba(245, 247, 250, .92));
|
||||
}
|
||||
|
||||
.hero {
|
||||
gap: .75rem;
|
||||
display: flex;
|
||||
padding-top: 3rem;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tag,
|
||||
.label,
|
||||
.desc {
|
||||
margin: 0;
|
||||
color: var(--app-sub);
|
||||
}
|
||||
|
||||
.tag {
|
||||
font-size: .875rem;
|
||||
letter-spacing: .08em;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
font-size: 2rem;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: .95rem;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.form-card {
|
||||
gap: 1rem;
|
||||
display: flex;
|
||||
padding: 1.2rem;
|
||||
border-radius: 1.25rem;
|
||||
flex-direction: column;
|
||||
border: 1px solid var(--app-line);
|
||||
background: rgba(255, 255, 255, .86);
|
||||
box-shadow: 0 .75rem 2rem rgba(17, 32, 56, .08);
|
||||
backdrop-filter: blur(14px);
|
||||
}
|
||||
|
||||
.group {
|
||||
gap: .5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: .875rem;
|
||||
}
|
||||
|
||||
.protocols {
|
||||
gap: .75rem;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
:global(.theme-dark) .server-index {
|
||||
.panel {
|
||||
background:
|
||||
radial-gradient(circle at top right, rgba(110, 168, 255, .2), transparent 34%),
|
||||
linear-gradient(180deg, rgba(16, 20, 24, .96), rgba(12, 16, 20, .96));
|
||||
}
|
||||
|
||||
.form-card {
|
||||
background: rgba(26, 33, 41, .86);
|
||||
box-shadow: 0 .75rem 2rem rgba(0, 0, 0, .28);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
25
src/pages/tabs/FilePage.vue
Normal file
25
src/pages/tabs/FilePage.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<route-placeholder title="文件页" description="这里保留为文件管理入口,用于验证首页标签、布局容器和二级详情页切换。" />
|
||||
<t-button block theme="primary" @click="openFileDetail">
|
||||
打开文件详情页
|
||||
</t-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const router = useRouter();
|
||||
|
||||
async function openFileDetail(): Promise<void> {
|
||||
await router.push("/files/detail/demo-file");
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.page {
|
||||
gap: 1rem;
|
||||
display: flex;
|
||||
padding: 1.2rem;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
6
src/pages/tabs/ReaderPage.vue
Normal file
6
src/pages/tabs/ReaderPage.vue
Normal file
@@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<route-placeholder title="阅读页" description="该页面暂时不在 tab 中展示,只保留占位,后续可按需恢复。" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
25
src/pages/tabs/ServerStatusPage.vue
Normal file
25
src/pages/tabs/ServerStatusPage.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<route-placeholder title="状态页" description="这里保留为服务器状态入口,用于验证底部标签切换和服务日志页前进返回动画。" />
|
||||
<t-button block variant="outline" @click="openServerLogs">
|
||||
查看服务日志页
|
||||
</t-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const router = useRouter();
|
||||
|
||||
async function openServerLogs(): Promise<void> {
|
||||
await router.push("/server/logs");
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.page {
|
||||
gap: 1rem;
|
||||
display: flex;
|
||||
padding: 1.2rem;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
208
src/pages/tabs/SettingsPage.vue
Normal file
208
src/pages/tabs/SettingsPage.vue
Normal file
@@ -0,0 +1,208 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<section class="card">
|
||||
<div class="head">
|
||||
<p class="tag">连接配置</p>
|
||||
<h2 class="title">服务器连接</h2>
|
||||
<p class="desc">这里的配置会持久化保存,缺失时应用会强制回到连接引导页。</p>
|
||||
</div>
|
||||
|
||||
<div class="group">
|
||||
<p class="label">协议</p>
|
||||
<div class="protocols">
|
||||
<t-button
|
||||
size="small"
|
||||
theme="primary"
|
||||
:variant="form.protocol === 'http' ? 'base' : 'outline'"
|
||||
@click="setProtocol('http')"
|
||||
>
|
||||
HTTP
|
||||
</t-button>
|
||||
<t-button
|
||||
size="small"
|
||||
theme="primary"
|
||||
:variant="form.protocol === 'https' ? 'base' : 'outline'"
|
||||
@click="setProtocol('https')"
|
||||
>
|
||||
HTTPS
|
||||
</t-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="group">
|
||||
<p class="label">主机地址</p>
|
||||
<t-input v-model="form.host" clearable placeholder="例如 192.168.1.100 或 nas.local" />
|
||||
</div>
|
||||
|
||||
<div class="group">
|
||||
<p class="label">端口</p>
|
||||
<t-input v-model="form.port" clearable type="number" placeholder="例如 8080" />
|
||||
</div>
|
||||
|
||||
<div class="group">
|
||||
<p class="label">访问令牌</p>
|
||||
<t-input v-model="form.token" clearable placeholder="请输入 token" />
|
||||
</div>
|
||||
|
||||
<t-button block theme="primary" @click="saveConnect">
|
||||
保存连接配置
|
||||
</t-button>
|
||||
<t-button block variant="outline" @click="resetConnect">
|
||||
清空连接配置
|
||||
</t-button>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<div class="head">
|
||||
<p class="tag">主题</p>
|
||||
<h2 class="title">界面模式</h2>
|
||||
<p class="desc">当前仅提供浅色、深色和跟随系统三种模式。</p>
|
||||
</div>
|
||||
|
||||
<div class="modes">
|
||||
<t-button
|
||||
v-for="item in themeModeList"
|
||||
:key="item.value"
|
||||
theme="primary"
|
||||
:variant="globalUIStore.themeMode === item.value ? 'base' : 'outline'"
|
||||
@click="globalUIStore.setThemeMode(item.value)"
|
||||
>
|
||||
<span v-text="item.label"></span>
|
||||
</t-button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Toast } from "tdesign-mobile-vue";
|
||||
import { useGlobalUIStore, type ThemeMode } from "@/store/globalUIStore";
|
||||
import type { ConnectProtocol, ConnectSetting } from "@/store/settingStore";
|
||||
import { useSettingStore } from "@/store/settingStore";
|
||||
|
||||
const globalUIStore = useGlobalUIStore();
|
||||
const settingStore = useSettingStore();
|
||||
|
||||
const themeModeList: Array<{ label: string; value: ThemeMode }> = [
|
||||
{ label: "浅色", value: "light" },
|
||||
{ label: "深色", value: "dark" },
|
||||
{ label: "跟随系统", value: "system" }
|
||||
];
|
||||
|
||||
const form = reactive<ConnectSetting>({
|
||||
protocol: "http",
|
||||
host: "",
|
||||
port: "",
|
||||
token: ""
|
||||
});
|
||||
|
||||
watch(
|
||||
() => settingStore.connect,
|
||||
(connect) => {
|
||||
Object.assign(form, connect);
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
function setProtocol(protocol: ConnectProtocol): void {
|
||||
form.protocol = protocol;
|
||||
}
|
||||
|
||||
function validateConnect(): boolean {
|
||||
const host = form.host.trim();
|
||||
const port = form.port.trim();
|
||||
const token = form.token.trim();
|
||||
|
||||
if (!host || !port || !token) {
|
||||
Toast({
|
||||
theme: "warning",
|
||||
message: "请完整填写连接配置"
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function saveConnect(): void {
|
||||
if (!validateConnect()) {
|
||||
return;
|
||||
}
|
||||
|
||||
settingStore.setConnect(form);
|
||||
Toast({
|
||||
theme: "success",
|
||||
message: "连接配置已保存"
|
||||
});
|
||||
}
|
||||
|
||||
function resetConnect(): void {
|
||||
settingStore.resetConnect();
|
||||
Object.assign(form, settingStore.connect);
|
||||
Toast({
|
||||
theme: "success",
|
||||
message: "连接配置已清空"
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.page {
|
||||
gap: 1rem;
|
||||
display: flex;
|
||||
padding: 1rem;
|
||||
flex-direction: column;
|
||||
|
||||
.card {
|
||||
gap: 1rem;
|
||||
display: flex;
|
||||
padding: 1rem;
|
||||
border-radius: 1rem;
|
||||
flex-direction: column;
|
||||
border: 1px solid var(--app-line);
|
||||
background: var(--app-card);
|
||||
box-shadow: 0 .35rem 1rem rgba(17, 32, 56, .05);
|
||||
}
|
||||
|
||||
.head,
|
||||
.group {
|
||||
gap: .5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tag,
|
||||
.label,
|
||||
.desc {
|
||||
margin: 0;
|
||||
color: var(--app-sub);
|
||||
}
|
||||
|
||||
.tag,
|
||||
.label {
|
||||
font-size: .875rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.desc {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.protocols,
|
||||
.modes {
|
||||
gap: .75rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
:global(.theme-dark) .page {
|
||||
.card {
|
||||
box-shadow: 0 .35rem 1rem rgba(0, 0, 0, .2);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user