refactor(h5): 重构为 mobile/pc/shared 三层目录并实现双端入口分发
- 目录三层化:移动端代码集中 src/mobile/(App.vue/router/layout/views), PC 端 src/pc/,公共层 src/shared/(api/components/store/utils/styles 等); 批量改写 @/<共享目录> → @/shared/<共享目录> - 双端入口:index.html 内联脚本探测设备写 window.__DEVICE__,main.ts 据此 挂载移动端(#app) 或 PC(#pc-app),零闪烁;PC 走 scoped px, postcss-mobile-forever 排除 src/pc/ - 路由切 history 模式,两端首页统一 /,/home* 兼容重定向; PC catch-all 渲染独立 NotFound 组件(非 redirect) - 构建配置同步:svg iconDirs、less additionalData、Components dirs、warmup 路径
@@ -20,6 +20,8 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
|
||||
vue(),
|
||||
// 按需引入VantUi且自动创建组件声明
|
||||
Components({
|
||||
// 共享组件目录(移动端使用;PC 端独立,不扫描此处)
|
||||
dirs: ['src/shared/components'],
|
||||
dts: true,
|
||||
resolvers: [VantResolver()],
|
||||
types: [],
|
||||
|
||||
@@ -9,7 +9,7 @@ import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
|
||||
export function configSvgIconsPlugin(isBuild: boolean) {
|
||||
// 指定需要缓存的图标文件夹
|
||||
const svgIconsPlugin = createSvgIconsPlugin({
|
||||
iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
|
||||
iconDirs: [path.resolve(process.cwd(), 'src/shared/assets/icons')],
|
||||
// 是否压缩
|
||||
svgoOptions: isBuild,
|
||||
// 指定symbolId格式
|
||||
|
||||
18
index.html
@@ -10,6 +10,22 @@
|
||||
<div id="app">
|
||||
<script>
|
||||
;(() => {
|
||||
// 设备类型探测:移动端 UA(含 iPadOS 13+ 伪装桌面 UA 的情况)判定为 mobile,其余为 pc
|
||||
// 开发期可用 ?device=pc|mobile 查询参数强制覆盖,便于切端调试
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
const override = params.get('device')
|
||||
const ua = navigator.userAgent
|
||||
const isTouch = navigator.maxTouchPoints > 0
|
||||
const isMobileUA =
|
||||
/(Mobile|Android|iPhone|iPod|Windows Phone)/i.test(ua) || (isTouch && /Macintosh/i.test(ua))
|
||||
window.__DEVICE__ = override === 'pc' || override === 'mobile' ? override : isMobileUA ? 'mobile' : 'pc'
|
||||
|
||||
// PC 端只挂载 #pc-app,隐藏 #app 内的移动端首屏 loading,避免残留转圈
|
||||
if (window.__DEVICE__ === 'pc') {
|
||||
const appEl = document.getElementById('app')
|
||||
if (appEl) appEl.style.display = 'none'
|
||||
}
|
||||
|
||||
// 主题色从本地存储读取(用户可配置项持久化)
|
||||
const { appTheme = '#5d9dfe' } = JSON.parse(window.localStorage.getItem('DESIGN-SETTING')) || {}
|
||||
|
||||
@@ -125,6 +141,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- PC 端挂载点(与移动端 #app 并列,同一时刻仅其一被挂载) -->
|
||||
<div id="pc-app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -45,8 +45,9 @@ export default {
|
||||
autoprefixer(),
|
||||
viewport({
|
||||
...baseViewportOpts,
|
||||
// 只将 vant 转为 375 设计稿的 viewport,其它样式的视图宽度为 750
|
||||
// viewportWidth: file => (file.includes('node_modules/vant/') ? 375 : 750),
|
||||
// PC 端源码不转 vw:PC 页面用 scoped 原生 px + 媒体查询写样式
|
||||
// 见 AGENTS.md「PC/移动端双端支持」约束
|
||||
exclude: [/src\/pc\//],
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
47
src/main.ts
@@ -1,40 +1,23 @@
|
||||
import { createApp } from 'vue'
|
||||
import { setupStore } from '@/store'
|
||||
import { useDesignSettingWithOut } from '@/store/modules/designSetting'
|
||||
import App from './App.vue'
|
||||
import router, { setupRouter } from './router'
|
||||
import { createMobileApp } from '@/mobile/app'
|
||||
import { createPCApp } from '@/pc/app'
|
||||
// UnoCSS 全局样式与重置在入口统一导入一次,PC/移动端共用
|
||||
import 'virtual:uno.css'
|
||||
|
||||
import 'vant/es/toast/style'
|
||||
|
||||
import 'vant/es/dialog/style'
|
||||
import 'vant/es/notify/style'
|
||||
import 'vant/es/image-preview/style'
|
||||
// https://unocss.dev/guide/style-reset#tailwind-compat
|
||||
// 此重置基于 Tailwind 重置,减去按钮的背景颜色覆盖,以避免与 UI 框架发生冲突。请参阅链接的问题。
|
||||
// 此重置基于 Tailwind 重置,减去按钮的背景颜色覆盖,以避免与 UI 框架发生冲突。
|
||||
import '@unocss/reset/tailwind-compat.css'
|
||||
// Register icon sprite
|
||||
import 'virtual:svg-icons-register'
|
||||
|
||||
// 开发环境启用 vconsole 移动端调试面板(由 VITE_V_CONSOLE 控制,默认开启)
|
||||
if (import.meta.env.DEV && import.meta.env.VITE_V_CONSOLE !== 'false') {
|
||||
import('vconsole').then(({ default: VConsole }) => {
|
||||
// eslint-disable-next-line no-new -- vconsole 以副作用方式实例化以挂载调试面板
|
||||
new VConsole()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用入口分发器
|
||||
*
|
||||
* 由 index.html 内联脚本在 Vue 挂载前写入 window.__DEVICE__('pc' | 'mobile'),
|
||||
* 此处据此挂载对应应用,首屏即为正确设备 UI,无重定向、无布局闪烁。
|
||||
* - mobile → 挂载到 #app(受 postcss-mobile-forever 限宽 600px 居中)
|
||||
* - pc → 挂载到 #pc-app(全宽,px 不被转 vw)
|
||||
*/
|
||||
async function bootstrap() {
|
||||
const app = createApp(App)
|
||||
// 挂载状态管理
|
||||
setupStore(app)
|
||||
// 初始化全局主题:跟随系统 prefers-color-scheme(不支持手动修改)
|
||||
useDesignSettingWithOut().initSystemListener()
|
||||
// 挂载路由
|
||||
setupRouter(app)
|
||||
await router.isReady()
|
||||
// 路由准备就绪后挂载APP实例
|
||||
app.mount('#app', true)
|
||||
const isPC = window.__DEVICE__ === 'pc'
|
||||
const app = isPC ? await createPCApp() : await createMobileApp()
|
||||
app.mount(isPC ? '#pc-app' : '#app', true)
|
||||
}
|
||||
|
||||
void bootstrap()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { useDesignSetting } from '@/hooks/setting/useDesignSetting'
|
||||
import { useRouteStore } from '@/store/modules/route'
|
||||
import { darken, lighten } from '@/utils'
|
||||
import { useDesignSetting } from '@/shared/hooks/setting/useDesignSetting'
|
||||
import { useRouteStore } from '@/shared/store/modules/route'
|
||||
import { darken, lighten } from '@/shared/utils'
|
||||
|
||||
const routeStore = useRouteStore()
|
||||
const { getDarkMode, getAppTheme, getIsPageAnimate, getPageAnimateType } = useDesignSetting()
|
||||
@@ -73,5 +73,5 @@ const getTransitionName = computed(() => {
|
||||
</template>
|
||||
|
||||
<style lang="less">
|
||||
@import './styles/index.less';
|
||||
@import '../shared/styles/index.less';
|
||||
</style>
|
||||
36
src/mobile/app.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { createApp } from 'vue'
|
||||
import MobileApp from '@/mobile/App.vue'
|
||||
import router, { setupRouter } from '@/mobile/router'
|
||||
import { setupStore } from '@/shared/store'
|
||||
import { useDesignSettingWithOut } from '@/shared/store/modules/designSetting'
|
||||
import 'vant/es/toast/style'
|
||||
|
||||
import 'vant/es/dialog/style'
|
||||
import 'vant/es/notify/style'
|
||||
import 'vant/es/image-preview/style'
|
||||
// Register icon sprite
|
||||
import 'virtual:svg-icons-register'
|
||||
|
||||
// 开发环境启用 vconsole 移动端调试面板(由 VITE_V_CONSOLE 控制,默认开启)
|
||||
if (import.meta.env.DEV && import.meta.env.VITE_V_CONSOLE !== 'false') {
|
||||
import('vconsole').then(({ default: VConsole }) => {
|
||||
// eslint-disable-next-line no-new -- vconsole 以副作用方式实例化以挂载调试面板
|
||||
new VConsole()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建移动端应用实例
|
||||
* 挂载点:#app(受 postcss-mobile-forever 的 appSelector 限宽 600px 居中)
|
||||
*/
|
||||
export async function createMobileApp() {
|
||||
const app = createApp(MobileApp)
|
||||
// 挂载状态管理
|
||||
setupStore(app)
|
||||
// 初始化全局主题:跟随系统 prefers-color-scheme(不支持手动修改)
|
||||
useDesignSettingWithOut().initSystemListener()
|
||||
// 挂载路由
|
||||
setupRouter(app)
|
||||
await router.isReady()
|
||||
return app
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useDesignSettingStore } from '@/store/modules/designSetting'
|
||||
import { useRouteStore } from '@/store/modules/route'
|
||||
import { useDesignSettingStore } from '@/shared/store/modules/designSetting'
|
||||
import { useRouteStore } from '@/shared/store/modules/route'
|
||||
|
||||
defineOptions({ name: 'BasicLayout' })
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import { PageEnum } from '@/enums/pageEnum'
|
||||
|
||||
const Layout = () => import('@/layout/index.vue')
|
||||
const Layout = () => import('@/mobile/layout/index.vue')
|
||||
|
||||
// 404 on a page
|
||||
export const ErrorPageRoute: RouteRecordRaw = {
|
||||
path: '/:path(.*)*',
|
||||
name: PageEnum.ERROR_PAGE_NAME,
|
||||
name: 'ErrorPage',
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: 'ErrorPage',
|
||||
@@ -16,7 +15,7 @@ export const ErrorPageRoute: RouteRecordRaw = {
|
||||
{
|
||||
path: '/:path(.*)*',
|
||||
name: 'ErrorPageSon',
|
||||
component: () => import('@/views/exception/404.vue'),
|
||||
component: () => import('@/mobile/views/exception/404.vue'),
|
||||
meta: {
|
||||
title: 'ErrorPage',
|
||||
hideBreadcrumb: true,
|
||||
@@ -24,12 +23,3 @@ export const ErrorPageRoute: RouteRecordRaw = {
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const RootRoute: RouteRecordRaw = {
|
||||
path: '/',
|
||||
name: 'Root',
|
||||
redirect: PageEnum.BASE_HOME,
|
||||
meta: {
|
||||
title: 'Root',
|
||||
},
|
||||
}
|
||||
@@ -1,15 +1,13 @@
|
||||
import type { App } from 'vue'
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
import { ErrorPageRoute, RootRoute } from '@/router/base'
|
||||
import { useRouteStoreWithOut } from '@/store/modules/route'
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { useRouteStoreWithOut } from '@/shared/store/modules/route'
|
||||
import { ErrorPageRoute } from './base'
|
||||
import routeModuleList from './modules'
|
||||
import { createRouterGuards } from './router-guards'
|
||||
|
||||
// 菜单
|
||||
|
||||
// 普通路由
|
||||
export const constantRouter: RouteRecordRaw[] = [RootRoute, ErrorPageRoute]
|
||||
export const constantRouter: RouteRecordRaw[] = [ErrorPageRoute]
|
||||
|
||||
const routeStore = useRouteStoreWithOut()
|
||||
|
||||
@@ -17,7 +15,7 @@ routeStore.setMenus(routeModuleList)
|
||||
routeStore.setRouters(constantRouter.concat(routeModuleList))
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(''),
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: constantRouter.concat(...routeModuleList),
|
||||
strict: true,
|
||||
scrollBehavior: () => ({ left: 0, top: 0 }),
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
|
||||
const Layout = () => import('@/layout/index.vue')
|
||||
const Layout = () => import('@/mobile/layout/index.vue')
|
||||
|
||||
// 业务路由模块(等待业务开发,当前仅保留占位首页)
|
||||
const routeModuleList: Array<RouteRecordRaw> = [
|
||||
// 首页(根路径 /,与 PC 端首页地址统一)
|
||||
{
|
||||
path: '/home',
|
||||
path: '/',
|
||||
name: 'Home',
|
||||
redirect: '/home/index',
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: '首页',
|
||||
@@ -15,12 +15,12 @@ const routeModuleList: Array<RouteRecordRaw> = [
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
path: '',
|
||||
name: 'HomePage',
|
||||
meta: {
|
||||
keepAlive: true,
|
||||
},
|
||||
component: () => import('@/views/home/index.vue'),
|
||||
component: () => import('@/mobile/views/home/index.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Router } from 'vue-router'
|
||||
import NProgress from 'nprogress'
|
||||
import { isNavigationFailure } from 'vue-router'
|
||||
import { useRouteStoreWithOut } from '@/store/modules/route'
|
||||
import { useRouteStoreWithOut } from '@/shared/store/modules/route'
|
||||
import 'nprogress/nprogress.css'
|
||||
|
||||
NProgress.configure({ parent: '#app' })
|
||||
@@ -10,7 +10,7 @@ function goHome() {
|
||||
<template>
|
||||
<div class="page-container flex flex-col justify-center">
|
||||
<div class="text-center">
|
||||
<img src="~@/assets/icons/exception/404.svg" alt="">
|
||||
<img src="~@/shared/assets/icons/exception/404.svg" alt="">
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<p class="m-4 text-base">
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import Logo from '@/components/Logo.vue'
|
||||
import Logo from '@/shared/components/Logo.vue'
|
||||
|
||||
defineOptions({ name: 'HomePage' })
|
||||
|
||||
7
src/pc/App.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
defineOptions({ name: 'PcApp' })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RouterView />
|
||||
</template>
|
||||
32
src/pc/app.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { App } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||
import { createApp } from 'vue'
|
||||
import PcApp from '@/pc/App.vue'
|
||||
import { pcRouter, setupPCRouter } from '@/pc/router'
|
||||
|
||||
// PC 端独立的 pinia 实例(与移动端状态隔离,同一时刻仅一套应用运行)
|
||||
const pcStore = createPinia()
|
||||
pcStore.use(piniaPluginPersistedstate)
|
||||
|
||||
function setupPCStore(app: App) {
|
||||
app.use(pcStore)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 PC 端应用实例
|
||||
*
|
||||
* 挂载点:#pc-app(独立于 #app,不受 postcss-mobile-forever 的 appSelector 限宽影响)
|
||||
*
|
||||
* 样式约束:PC 页面所有尺寸用 scoped <style>(原生 px + 媒体查询),
|
||||
* 禁止使用 UnoCSS 的 px 原子类(如 p-4 / w-100 / text-2xl),
|
||||
* 它们会进入全局 UnoCSS 样式被 mobile-forever 转 vw,在宽屏下错乱。
|
||||
* UnoCSS 在 PC 仅限用非长度类(flex / grid / hidden / 颜色 / 文字对齐等)。
|
||||
*/
|
||||
export async function createPCApp() {
|
||||
const app = createApp(PcApp)
|
||||
setupPCStore(app)
|
||||
setupPCRouter(app)
|
||||
await pcRouter.isReady()
|
||||
return app
|
||||
}
|
||||
51
src/pc/layout/index.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<script setup lang="ts">
|
||||
defineOptions({ name: 'PcLayout' })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="pc-shell">
|
||||
<header class="pc-header">
|
||||
<span class="pc-header__title">DaxPay · PC</span>
|
||||
</header>
|
||||
<main class="pc-main">
|
||||
<RouterView />
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* PC 端样式示范:scoped 原生 px + 媒体查询,不使用 UnoCSS 的 px 原子类 */
|
||||
.pc-shell {
|
||||
min-height: 100vh;
|
||||
background: #f5f6f8;
|
||||
}
|
||||
|
||||
.pc-header {
|
||||
height: 56px;
|
||||
padding: 0 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #ffffff;
|
||||
border-bottom: 1px solid #ebedf0;
|
||||
}
|
||||
|
||||
.pc-header__title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
}
|
||||
|
||||
.pc-main {
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 32px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.pc-main {
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
32
src/pc/router/index.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { App } from 'vue'
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
|
||||
// PC 端路由树(独立于移动端,按需在此扩展业务路由,例如收银台 PC 版)
|
||||
// 内页(收银台等)建议套 PcLayout;首页为全屏独立页,贴合移动端首页视觉
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'PcHome',
|
||||
component: () => import('@/pc/views/Home.vue'),
|
||||
},
|
||||
// 兜底:移动端专属路径(如 /home/index)或任何未匹配路径,渲染 PC 404 页
|
||||
// 用 component 而非 redirect——vue-router 5 下 catch-all + redirect 在初始导航不触发
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'PcNotFound',
|
||||
component: () => import('@/pc/views/NotFound.vue'),
|
||||
},
|
||||
]
|
||||
|
||||
const pcRouter = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes,
|
||||
scrollBehavior: () => ({ left: 0, top: 0 }),
|
||||
})
|
||||
|
||||
export function setupPCRouter(app: App) {
|
||||
app.use(pcRouter)
|
||||
}
|
||||
|
||||
export { pcRouter }
|
||||
89
src/pc/views/Home.vue
Normal file
@@ -0,0 +1,89 @@
|
||||
<script setup lang="ts">
|
||||
defineOptions({ name: 'PcHome' })
|
||||
|
||||
// 由 vite define 注入的项目信息(与移动端首页一致)
|
||||
const { pkg, lastBuildTime } = __APP_INFO__
|
||||
const version = pkg.version
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="pc-home">
|
||||
<div class="pc-home__body">
|
||||
<div class="pc-home__welcome">
|
||||
<!-- DaxPay 文字徽标(与移动端首页默认 logo 一致,public/logo.svg) -->
|
||||
<img class="pc-home__logo" src="/logo.svg" alt="DaxPay">
|
||||
<!-- 欢迎语 -->
|
||||
<div class="pc-home__title">
|
||||
欢迎使用 DaxPay
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 底部项目信息 -->
|
||||
<div class="pc-home__footer">
|
||||
<p>版本号: v{{ version }}</p>
|
||||
<p>构建时间: {{ lastBuildTime }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* PC 端首页:scoped 原生 px + 媒体查询,复刻移动端首页布局(全屏居中 + 底部信息) */
|
||||
.pc-home {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #f7f8fa;
|
||||
}
|
||||
|
||||
.pc-home__body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.pc-home__welcome {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pc-home__logo {
|
||||
width: 180px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.pc-home__title {
|
||||
margin-top: 16px;
|
||||
text-align: center;
|
||||
font-size: 28px;
|
||||
font-weight: 900;
|
||||
color: #1d2129;
|
||||
}
|
||||
|
||||
.pc-home__footer {
|
||||
position: fixed;
|
||||
bottom: 24px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
opacity: 0.5;
|
||||
line-height: 1.6;
|
||||
color: #4e5969;
|
||||
}
|
||||
|
||||
.pc-home__footer p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.pc-home__logo {
|
||||
width: 140px;
|
||||
}
|
||||
|
||||
.pc-home__title {
|
||||
font-size: 22px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
80
src/pc/views/NotFound.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<script lang="ts" setup>
|
||||
defineOptions({ name: 'PcNotFound' })
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
function goHome() {
|
||||
router.push('/')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="pc-notfound">
|
||||
<img class="pc-notfound__logo" src="/logo.svg" alt="DaxPay">
|
||||
<p class="pc-notfound__code">
|
||||
404
|
||||
</p>
|
||||
<p class="pc-notfound__text">
|
||||
抱歉,你访问的页面不存在
|
||||
</p>
|
||||
<button class="pc-notfound__btn" type="button" @click="goHome">
|
||||
返回首页
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* PC 端 404 页:scoped 原生 px + 媒体查询,不使用 UnoCSS 的 px 原子类 */
|
||||
.pc-notfound {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f7f8fa;
|
||||
}
|
||||
|
||||
.pc-notfound__logo {
|
||||
width: 160px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.pc-notfound__code {
|
||||
margin: 24px 0 8px;
|
||||
font-size: 56px;
|
||||
font-weight: 900;
|
||||
line-height: 1;
|
||||
color: #1d2129;
|
||||
}
|
||||
|
||||
.pc-notfound__text {
|
||||
margin: 0 0 32px;
|
||||
font-size: 16px;
|
||||
color: #4e5969;
|
||||
}
|
||||
|
||||
.pc-notfound__btn {
|
||||
padding: 10px 28px;
|
||||
font-size: 14px;
|
||||
color: #ffffff;
|
||||
background: #5d9dfe;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.pc-notfound__btn:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.pc-notfound__logo {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.pc-notfound__code {
|
||||
font-size: 44px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { appThemeList } from '@/settings/designSetting'
|
||||
import { useDesignSettingStore } from '@/store/modules/designSetting'
|
||||
import { hexToRgba } from '@/utils'
|
||||
import { appThemeList } from '@/shared/settings/designSetting'
|
||||
import { useDesignSettingStore } from '@/shared/store/modules/designSetting'
|
||||
import { hexToRgba } from '@/shared/utils'
|
||||
|
||||
defineOptions({ name: 'Logo' })
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/* eslint-disable ts/no-duplicate-enum-values */
|
||||
export enum PageEnum {
|
||||
// 首页
|
||||
BASE_HOME = '/home',
|
||||
BASE_HOME_REDIRECT = '/home',
|
||||
BASE_HOME = '/',
|
||||
BASE_HOME_REDIRECT = '/',
|
||||
// 错误
|
||||
ERROR_PAGE_NAME = 'ErrorPage',
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { GlobConfig } from '#/config'
|
||||
import { getAppEnvConfig } from '@/utils/env'
|
||||
import { warn } from '@/utils/log'
|
||||
import { getAppEnvConfig } from '@/shared/utils/env'
|
||||
import { warn } from '@/shared/utils/log'
|
||||
|
||||
export function useGlobSetting(): Readonly<GlobConfig> {
|
||||
const {
|
||||
@@ -1,5 +1,5 @@
|
||||
import { computed } from 'vue'
|
||||
import { useDesignSettingStore } from '@/store/modules/designSetting'
|
||||
import { useDesignSettingStore } from '@/shared/store/modules/designSetting'
|
||||
|
||||
export function useDesignSetting() {
|
||||
const designStore = useDesignSettingStore()
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { DesignSettingState } from '@/settings/designSetting'
|
||||
import type { DesignSettingState } from '@/shared/settings/designSetting'
|
||||
import { defineStore } from 'pinia'
|
||||
import designSetting from '@/settings/designSetting'
|
||||
import { store } from '@/store'
|
||||
import designSetting from '@/shared/settings/designSetting'
|
||||
import { store } from '@/shared/store'
|
||||
|
||||
const { systemPrefersDark, appTheme, appThemeList, isPageAnimate, pageAnimateType } = designSetting
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import { defineStore } from 'pinia'
|
||||
import { store } from '@/store'
|
||||
import { store } from '@/shared/store'
|
||||
|
||||
export interface IRouteState {
|
||||
menus: RouteRecordRaw[]
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { GlobEnvConfig } from '#/config'
|
||||
import { warn } from '@/utils/log'
|
||||
import { getConfigFileName } from '../../build/getConfigFileName'
|
||||
import { warn } from '@/shared/utils/log'
|
||||
import { getConfigFileName } from '../../../build/getConfigFileName'
|
||||
|
||||
import pkg from '../../package.json'
|
||||
import pkg from '../../../package.json'
|
||||
|
||||
export function getCommonStoragePrefix() {
|
||||
const { VITE_GLOB_APP_SHORT_NAME } = getAppEnvConfig()
|
||||
@@ -5,8 +5,8 @@ import type { CreateAxiosOptions, RequestOptions, Result, UploadFileParams } fro
|
||||
import axios from 'axios'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import qs from 'qs'
|
||||
import { ContentTypeEnum, RequestEnum } from '@/enums/httpEnum'
|
||||
import { isFunction } from '@/utils/is'
|
||||
import { ContentTypeEnum, RequestEnum } from '@/shared/enums/httpEnum'
|
||||
import { isFunction } from '@/shared/utils/is'
|
||||
|
||||
import { AxiosCanceler } from './axiosCancel'
|
||||
|
||||
@@ -3,7 +3,7 @@ import axios from 'axios'
|
||||
|
||||
import qs from 'qs'
|
||||
|
||||
import { isFunction } from '@/utils/is'
|
||||
import { isFunction } from '@/shared/utils/is'
|
||||
|
||||
// 声明一个 Map 用于存储每个请求的标识 和 取消函数
|
||||
let pendingMap = new Map<string, Canceler>()
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isObject, isString } from '@/utils/is'
|
||||
import { isObject, isString } from '@/shared/utils/is'
|
||||
|
||||
const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm'
|
||||
|
||||
@@ -4,11 +4,11 @@ import type { AxiosTransform } from './axiosTransform'
|
||||
import type { CreateAxiosOptions, RequestOptions, Result } from './types'
|
||||
import axios from 'axios'
|
||||
import { showDialog, showFailToast } from 'vant'
|
||||
import { ContentTypeEnum, RequestEnum, ResultEnum } from '@/enums/httpEnum'
|
||||
import { useGlobSetting } from '@/hooks/setting'
|
||||
import { deepMerge, isUrl } from '@/utils'
|
||||
import { isString } from '@/utils/is/'
|
||||
import { setObjToUrlParams } from '@/utils/urlUtils'
|
||||
import { ContentTypeEnum, RequestEnum, ResultEnum } from '@/shared/enums/httpEnum'
|
||||
import { useGlobSetting } from '@/shared/hooks/setting'
|
||||
import { deepMerge, isUrl } from '@/shared/utils'
|
||||
import { isString } from '@/shared/utils/is/'
|
||||
import { setObjToUrlParams } from '@/shared/utils/urlUtils'
|
||||
|
||||
import { VAxios } from './Axios'
|
||||
import { checkStatus } from './checkStatus'
|
||||
@@ -253,7 +253,7 @@ export const http = createAxios()
|
||||
|
||||
// 项目,多个不同 api 地址,直接在这里导出多个
|
||||
// src/api ts 里面接口,就可以单独使用这个请求,
|
||||
// import { httpTwo } from '@/utils/http/axios'
|
||||
// import { httpTwo } from '@/shared/utils/http/axios'
|
||||
// export const httpTwo = createAxios({
|
||||
// requestOptions: {
|
||||
// apiUrl: 'http://localhost:9001',
|
||||
8
types/global.d.ts
vendored
@@ -10,6 +10,14 @@ import type {
|
||||
// 在不是模块的文件中使用 declare global(即不包含import / export)是错误的,因为这样的文件中的所有内容都在全局范围内。
|
||||
|
||||
declare global {
|
||||
// 设备类型:由 index.html 内联脚本在 Vue 挂载前写入,'pc' | 'mobile'
|
||||
const __DEVICE__: 'pc' | 'mobile'
|
||||
|
||||
interface Window {
|
||||
// 设备类型:由 index.html 内联脚本在 Vue 挂载前写入,'pc' | 'mobile'
|
||||
__DEVICE__: 'pc' | 'mobile'
|
||||
}
|
||||
|
||||
const __APP_INFO__: {
|
||||
pkg: {
|
||||
name: string
|
||||
|
||||
@@ -131,7 +131,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
|
||||
modifyVars: {},
|
||||
javascriptEnabled: true,
|
||||
// 注入全局 less 变量
|
||||
additionalData: `@import "src/styles/var.less";`,
|
||||
additionalData: `@import "src/shared/styles/var.less";`,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -145,8 +145,8 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
|
||||
proxy: createProxy(VITE_PROXY),
|
||||
// 预热文件以降低启动期间的初始页面加载时长
|
||||
warmup: {
|
||||
// 预热的客户端文件:首页、views、 components
|
||||
clientFiles: ['./index.html', './src/{views,components}/*'],
|
||||
// 预热的客户端文件:首页、移动端 views、共享 components
|
||||
clientFiles: ['./index.html', './src/{mobile/views,shared/components}/*'],
|
||||
},
|
||||
// proxy: {
|
||||
// '/api': {
|
||||
|
||||