feat: init

This commit is contained in:
2026-01-16 13:01:40 +07:00
commit 5b6477559f
34 changed files with 13714 additions and 0 deletions

10
src/App.vue Normal file
View File

@@ -0,0 +1,10 @@
<script setup lang="ts">
</script>
<template>
<IonApp>
<Suspense>
<IonRouterOutlet />
</Suspense>
</IonApp>
</template>

View File

@@ -0,0 +1,47 @@
<script setup lang="ts">
import { cellular, chatboxEllipses, compass, personCircle, swapHorizontal } from "ionicons/icons";
const { t } = useI18n();
</script>
<template>
<ion-page>
<ion-tabs>
<ion-router-outlet />
<ion-tab-bar slot="bottom">
<ion-tab-button tab="home" href="/layout/home">
<div class="flex-col-center gap-1">
<ion-icon aria-hidden="true" :icon="compass" class="icon" />
<ion-label>{{ t('tabs.home') }}</ion-label>
</div>
</ion-tab-button>
<ion-tab-button tab="notify" href="/layout/notify">
<div class="flex-col-center gap-1">
<ion-icon aria-hidden="true" :icon="chatboxEllipses" class="icon" />
<ion-label>{{ t('tabs.notify') }}</ion-label>
</div>
</ion-tab-button>
<ion-tab-button tab="user" href="/layout/user">
<div class="flex-col-center gap-1">
<ion-icon aria-hidden="true" :icon="personCircle" class="icon" />
<ion-label>{{ t('tabs.user') }}</ion-label>
</div>
</ion-tab-button>
</ion-tab-bar>
</ion-tabs>
</ion-page>
</template>
<style scoped>
ion-tab-bar {
height: 60px;
--background: var(--ion-background-color);
box-shadow: 0px 0px 12px rgba(45, 213, 90, 0.21);
padding-bottom: var(--ion-safe-area-bottom);
}
.icon {
font-size: 1.5rem;
}
</style>

15
src/locales/index.ts Normal file
View File

@@ -0,0 +1,15 @@
import { createI18n } from "vue-i18n";
import zhCN from "./zh-CN.json";
export type MessageSchema = typeof zhCN;
const i18n = createI18n<MessageSchema, "zh-CN">({
legacy: false,
locale: "zh-CN",
fallbackLocale: "zh-CN",
messages: {
"zh-CN": zhCN,
},
});
export { i18n };

10
src/locales/zh-CN.json Normal file
View File

@@ -0,0 +1,10 @@
{
"tabs": {
"home": "首页",
"notify": "通知",
"user": "我的"
},
"home": {
"title": "首页"
}
}

84
src/main.ts Normal file
View File

@@ -0,0 +1,84 @@
import { IonicVue } from "@ionic/vue";
import { createPinia } from "pinia";
import VConsole from "vconsole";
import { useRegisterSW } from "virtual:pwa-register/vue";
import { createApp } from "vue";
import App from "./App.vue";
import { i18n } from "./locales";
import { router } from "./router";
/* Core CSS required for Ionic components to work properly */
import "@ionic/vue/css/core.css";
/* Basic CSS for apps built with Ionic */
import "@ionic/vue/css/normalize.css";
import "@ionic/vue/css/structure.css";
import "@ionic/vue/css/typography.css";
/* Optional CSS utils that can be commented out */
import "@ionic/vue/css/padding.css";
import "@ionic/vue/css/float-elements.css";
import "@ionic/vue/css/text-alignment.css";
import "@ionic/vue/css/text-transformation.css";
import "@ionic/vue/css/flex-utils.css";
/**
* Ionic Dark Mode
* -----------------------------------------------------
* For more info, please see:
* https://ionicframework.com/docs/theming/dark-mode
*/
import "@ionic/vue/css/display.css";
// import "@ionic/vue/css/palettes/dark.system.css";
// import "@ionic/vue/css/palettes/dark.always.css";
// import "@ionic/vue/css/palettes/dark.class.css";
import "@/theme/index.css";
// 注册 PWA Service Worker使用 Vue 3 Composition API
const { offlineReady, needRefresh, updateServiceWorker } = useRegisterSW({
onRegistered(registration) {
console.log("[PWA] Service Worker registered:", registration);
},
onRegisterError(error) {
console.error("[PWA] Service Worker registration failed:", error);
},
immediate: true,
});
// 监听 PWA 状态
watch(offlineReady, (ready) => {
if (ready) {
console.log("[PWA] App ready to work offline.");
}
});
watch(needRefresh, (refresh) => {
if (refresh) {
console.log("[PWA] New content available, please refresh.");
}
});
if (import.meta.env.DEV) {
const vConsole = new VConsole();
globalThis.vConsole = vConsole;
console.log("VConsole is enabled in development mode.");
}
const pinia = createPinia();
const app = createApp(App)
.use(IonicVue, {
backButtonText: "返回",
mode: "ios",
statusTap: true,
swipeBackEnabled: true,
// rippleEffect: true,
// animated: false,
})
.use(pinia)
.use(router)
.use(i18n);
router.isReady().then(() => {
app.mount("#app");
});

18
src/router/auth.ts Normal file
View File

@@ -0,0 +1,18 @@
import type { RouteRecordRaw } from "vue-router";
const routes: Array<RouteRecordRaw> = [
{
path: "/auth/login",
component: () => import("@/views/auth/login/index.vue"),
},
{
path: "/auth/signup",
component: () => import("@/views/auth/signup/index.vue"),
},
{
path: "/auth/term",
component: () => import("@/views/auth/term.vue"),
},
];
export default routes;

7
src/router/guard.ts Normal file
View File

@@ -0,0 +1,7 @@
import type { Router } from "vue-router";
export function createRouterGuard(router: Router) {
router.beforeEach(async (to, from, next) => {
next();
});
}

35
src/router/index.ts Normal file
View File

@@ -0,0 +1,35 @@
import type { RouteRecordRaw } from "vue-router";
import { createRouter, createWebHistory } from "@ionic/vue-router";
import authRoutes from "./auth";
import { createRouterGuard } from "./guard";
const routes: Array<RouteRecordRaw> = [
{
path: "/",
redirect: "/layout/home",
},
{
path: "/:pathMatch(.*)*",
redirect: "/layout/home",
},
// ...authRoutes,
{
path: "/layout",
component: () => import("@/components/layout/default.vue"),
children: [
{
path: "home",
component: () => import("@/views/home/index.vue"),
},
],
},
];
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes,
});
createRouterGuard(router);
export { router };

3
src/theme/index.css Normal file
View File

@@ -0,0 +1,3 @@
@import "tailwindcss";
@config "../../tailwind.config.ts";
@import "./ionic.css"

4
src/theme/ionic.css Normal file
View File

@@ -0,0 +1,4 @@
.ion-toolbar {
--background: var(--ion-color-primary-contrast);
--min-height: 50px;
}

19
src/views/home/index.vue Normal file
View File

@@ -0,0 +1,19 @@
<script lang='ts' setup>
</script>
<template>
<ion-page>
<ion-header class="ion-no-border">
<ion-toolbar class="ion-toolbar">
<ion-button slot="start" fill="clear" class="z-1" @click="$router.push('/global-menu')">
<IconParkOutlineApplicationMenu slot="icon-only" />
</ion-button>
<ion-title>{{ $t('home.title') }}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content :fullscreen="true" class="ion-padding" />
</ion-page>
</template>
<style lang='css' scoped></style>

34
src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1,34 @@
/// <reference types="vite/client" />
/// <reference types="vite-plugin-pwa/client" />
/// <reference types="unplugin-icons/types/vue" />
/// <reference types="@cloudflare/workers-types" />
import type { MessageSchema } from "@/locales";
import {
DefineDateTimeFormat,
DefineLocaleMessage,
DefineNumberFormat,
} from "vue-i18n";
import "vue-router";
export {};
declare module "vue-i18n" {
// define the locale messages schema
export interface DefineLocaleMessage extends MessageSchema {
}
// define the datetime format schema
export interface DefineDateTimeFormat {
}
// define the number format schema
export interface DefineNumberFormat {
}
}
declare module "vue-router" {
interface RouteMeta {
requiresAuth: ? boolean;
}
}