init: 初始化 H5 和 大屏
This commit is contained in:
commit
ffaaa56af7
4
.env.development
Normal file
4
.env.development
Normal file
@ -0,0 +1,4 @@
|
||||
# VITE环境变量
|
||||
|
||||
# 开发环境
|
||||
VITE_API_BASE_URL=http://localhost:3000/api
|
||||
4
.env.production
Normal file
4
.env.production
Normal file
@ -0,0 +1,4 @@
|
||||
# VITE环境变量
|
||||
|
||||
# 生产环境
|
||||
VITE_API_BASE_URL=https://api.example.com
|
||||
22
.eslintrc.cjs
Normal file
22
.eslintrc.cjs
Normal file
@ -0,0 +1,22 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
node: true
|
||||
},
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:vue/vue3-recommended'
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module'
|
||||
},
|
||||
plugins: ['vue'],
|
||||
rules: {
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'vue/no-v-html': 'off'
|
||||
}
|
||||
}
|
||||
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
node_modules
|
||||
dist
|
||||
.DS_Store
|
||||
*.log
|
||||
.vscode
|
||||
.idea
|
||||
*.local
|
||||
276
README.md
Normal file
276
README.md
Normal file
@ -0,0 +1,276 @@
|
||||
# H5 Monorepo 工程
|
||||
|
||||
基于 pnpm workspace 的 Monorepo 项目,包含 Vue3 大屏和 Vue3 移动端两个子项目。
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
H5/
|
||||
├── packages/
|
||||
│ ├── screen/ # Vue3 大屏项目
|
||||
│ │ ├── src/
|
||||
│ │ │ ├── views/
|
||||
│ │ │ ├── components/
|
||||
│ │ │ ├── router/
|
||||
│ │ │ ├── store/
|
||||
│ │ │ └── styles/
|
||||
│ │ ├── vite.config.js
|
||||
│ │ └── package.json
|
||||
│ ├── mobile/ # Vue3 移动端项目
|
||||
│ │ ├── src/
|
||||
│ │ │ ├── views/
|
||||
│ │ │ ├── components/
|
||||
│ │ │ ├── router/
|
||||
│ │ │ └── styles/
|
||||
│ │ ├── vite.config.js
|
||||
│ │ └── package.json
|
||||
│ └── shared/ # 共享代码库
|
||||
│ ├── api/ # 接口封装
|
||||
│ ├── utils/ # 工具函数
|
||||
│ ├── config/ # 配置文件
|
||||
│ └── package.json
|
||||
├── pnpm-workspace.yaml
|
||||
├── package.json
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## 技术栈
|
||||
|
||||
### 大屏项目 (screen)
|
||||
- Vue 3
|
||||
- Vite 5
|
||||
- Vue Router 4
|
||||
- Pinia
|
||||
- ECharts 5
|
||||
- Vue-ECharts
|
||||
- Sass
|
||||
|
||||
### 移动端项目 (mobile)
|
||||
- Vue 3
|
||||
- Vite 5
|
||||
- Vue Router 4
|
||||
- Pinia
|
||||
- Vant 4(移动端 UI 组件库)
|
||||
- 自动按需引入组件
|
||||
- Sass
|
||||
|
||||
### 共享库 (shared)
|
||||
- Axios(请求封装)
|
||||
- 工具函数(防抖/节流/日期格式化/深拷贝等)
|
||||
- 统一配置管理
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 安装依赖
|
||||
|
||||
```bash
|
||||
# 安装 pnpm (如果还没安装)
|
||||
npm install -g pnpm
|
||||
|
||||
# 安装所有依赖
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### 开发命令
|
||||
|
||||
```bash
|
||||
# 启动大屏项目(端口:3000)
|
||||
pnpm dev:screen
|
||||
|
||||
# 启动 H5 移动端(端口:8080)
|
||||
pnpm dev:mobile
|
||||
|
||||
# 构建大屏项目
|
||||
pnpm build:screen
|
||||
|
||||
# 构建 H5 移动端
|
||||
pnpm build:mobile
|
||||
|
||||
# 构建所有项目
|
||||
pnpm build:all
|
||||
|
||||
# 代码检查
|
||||
pnpm lint
|
||||
```
|
||||
|
||||
## 项目说明
|
||||
|
||||
### 1. 大屏项目 (screen)
|
||||
|
||||
**访问地址:** `http://localhost:3000`
|
||||
|
||||
**特点:**
|
||||
- 适配大屏分辨率 (1920x1080)
|
||||
- 数据可视化(ECharts)
|
||||
- 暗色主题设计
|
||||
- 响应式布局
|
||||
|
||||
**目录结构:**
|
||||
```
|
||||
packages/screen/src/
|
||||
├── views/ # 页面组件
|
||||
├── components/ # 通用组件
|
||||
├── router/ # 路由配置
|
||||
├── store/ # Pinia 状态管理
|
||||
├── assets/ # 静态资源
|
||||
└── styles/ # 全局样式
|
||||
```
|
||||
|
||||
### 2. 移动端项目 (mobile)
|
||||
|
||||
**访问地址:** `http://localhost:8080`
|
||||
|
||||
**特点:**
|
||||
- 响应式设计,适配移动端
|
||||
- Vant UI 组件库
|
||||
- 自动按需引入,减小包体积
|
||||
- 支持 PWA
|
||||
- 适合嵌入第三方 APP
|
||||
|
||||
**可用组件:**
|
||||
- NavBar(导航栏)
|
||||
- Tabbar(底部导航)
|
||||
- Cell/CellGroup(单元格列表)
|
||||
- Grid(宫格)
|
||||
- Button(按钮)
|
||||
- Toast(轻提示)
|
||||
- Dialog(弹窗)
|
||||
- Image(图片)
|
||||
- 等 60+ 组件
|
||||
|
||||
**目录结构:**
|
||||
```
|
||||
packages/mobile/src/
|
||||
├── views/ # 页面组件
|
||||
├── components/ # 通用组件
|
||||
├── router/ # 路由配置
|
||||
├── store/ # Pinia 状态管理
|
||||
├── assets/ # 静态资源
|
||||
└── styles/ # 全局样式
|
||||
```
|
||||
|
||||
### 3. 共享库 (shared)
|
||||
|
||||
共享代码可在两个项目中复用:
|
||||
|
||||
```javascript
|
||||
// 在项目中引入 API
|
||||
import { getUserInfo, getDataList } from '@shared/api'
|
||||
|
||||
// 引入工具函数
|
||||
import { formatDate, debounce, throttle } from '@shared/utils'
|
||||
|
||||
// 引入配置
|
||||
import { API_CONFIG, APP_CONFIG } from '@shared/config'
|
||||
```
|
||||
|
||||
**可用工具函数:**
|
||||
- `formatDate()` - 日期格式化
|
||||
- `debounce()` - 防抖
|
||||
- `throttle()` - 节流
|
||||
- `deepClone()` - 深拷贝
|
||||
|
||||
## 环境变量
|
||||
|
||||
项目使用 `.env` 文件管理环境变量:
|
||||
|
||||
- `.env.development` - 开发环境
|
||||
- `.env.production` - 生产环境
|
||||
|
||||
**示例:**
|
||||
```bash
|
||||
# .env.development
|
||||
VITE_API_BASE_URL=http://localhost:3000/api
|
||||
```
|
||||
|
||||
## 开发建议
|
||||
|
||||
### 1. 共享代码复用
|
||||
|
||||
将通用的业务逻辑、API 接口、工具函数放在 `packages/shared` 中,避免重复代码。
|
||||
|
||||
### 2. 组件开发
|
||||
|
||||
- **大屏项目**:使用 ECharts 开发数据可视化组件
|
||||
- **移动端项目**:优先使用 Vant 组件,减少自定义开发
|
||||
|
||||
### 3. 样式规范
|
||||
|
||||
- 使用 Sass 预处理器
|
||||
- 遵循 BEM 命名规范
|
||||
- 组件样式使用 `scoped`
|
||||
|
||||
### 4. API 请求
|
||||
|
||||
统一使用 `@shared/api` 中封装的请求方法,自动处理 token、错误等。
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 本项目使用 **JavaScript**,未使用 TypeScript
|
||||
2. 使用 **pnpm** 作为包管理工具,不要使用 npm 或 yarn
|
||||
3. 大屏和移动端项目独立运行,通过 shared 包共享代码
|
||||
4. 移动端项目基于 Vue3 + Vant,**不是 uni-app**,仅支持 H5
|
||||
5. 如需扩展到小程序,建议使用 uni-app 或 Taro 重新搭建
|
||||
|
||||
## 部署说明
|
||||
|
||||
### 大屏项目部署
|
||||
|
||||
```bash
|
||||
# 构建
|
||||
pnpm build:screen
|
||||
|
||||
# 构建产物在 packages/screen/dist
|
||||
# 部署到 Nginx 或其他静态服务器
|
||||
```
|
||||
|
||||
### 移动端部署
|
||||
|
||||
```bash
|
||||
# 构建
|
||||
pnpm build:mobile
|
||||
|
||||
# 构建产物在 packages/mobile/dist
|
||||
# 可以嵌入到第三方 APP 的 WebView 中
|
||||
```
|
||||
|
||||
## 浏览器支持
|
||||
|
||||
### 大屏项目
|
||||
- Chrome >= 87
|
||||
- Firefox >= 78
|
||||
- Safari >= 14
|
||||
- Edge >= 88
|
||||
|
||||
### 移动端项目
|
||||
- iOS Safari >= 10
|
||||
- Android Chrome >= 5.0
|
||||
- 微信浏览器
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 1. 端口被占用
|
||||
|
||||
修改对应项目的 `vite.config.js` 中的 `server.port` 配置。
|
||||
|
||||
### 2. 组件按需引入不生效
|
||||
|
||||
移动端项目使用 `unplugin-vue-components` 自动按需引入 Vant 组件,无需手动引入。
|
||||
|
||||
### 3. 如何添加新页面?
|
||||
|
||||
```bash
|
||||
# 1. 在 src/views 中创建页面组件
|
||||
# 2. 在 src/router/index.js 中添加路由配置
|
||||
```
|
||||
|
||||
## 技术支持
|
||||
|
||||
- Vue 3 文档:https://cn.vuejs.org/
|
||||
- Vant 文档:https://vant-ui.github.io/vant/
|
||||
- ECharts 文档:https://echarts.apache.org/zh/index.html
|
||||
- Pinia 文档:https://pinia.vuejs.org/zh/
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT
|
||||
21
package.json
Normal file
21
package.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "h5-workspace",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "大屏 + H5 Monorepo 工程",
|
||||
"scripts": {
|
||||
"dev:screen": "pnpm --filter @h5/screen dev",
|
||||
"dev:mobile": "pnpm --filter @h5/mobile dev",
|
||||
"build:screen": "pnpm --filter @h5/screen build",
|
||||
"build:mobile": "pnpm --filter @h5/mobile build",
|
||||
"build:all": "pnpm -r build",
|
||||
"lint": "pnpm -r lint"
|
||||
},
|
||||
"keywords": ["monorepo", "vue3", "vant", "大屏", "h5"],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16.0.0",
|
||||
"pnpm": ">=8.0.0"
|
||||
}
|
||||
}
|
||||
14
packages/mobile/index.html
Normal file
14
packages/mobile/index.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0, viewport-fit=cover" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
|
||||
<title>H5移动端</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
25
packages/mobile/package.json
Normal file
25
packages/mobile/package.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "@h5/mobile",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.4.0",
|
||||
"vue-router": "^4.2.0",
|
||||
"pinia": "^2.1.0",
|
||||
"vant": "^4.8.0",
|
||||
"axios": "^1.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.0.0",
|
||||
"vite": "^5.0.0",
|
||||
"sass": "^1.70.0",
|
||||
"unplugin-vue-components": "^0.26.0",
|
||||
"unplugin-auto-import": "^0.17.0"
|
||||
}
|
||||
}
|
||||
17
packages/mobile/src/App.vue
Normal file
17
packages/mobile/src/App.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// App 根组件
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
background: #f7f8fa;
|
||||
}
|
||||
</style>
|
||||
13
packages/mobile/src/main.js
Normal file
13
packages/mobile/src/main.js
Normal file
@ -0,0 +1,13 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import router from './router'
|
||||
import App from './App.vue'
|
||||
import 'vant/lib/index.css'
|
||||
import './styles/index.css'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
|
||||
app.mount('#app')
|
||||
21
packages/mobile/src/router/index.js
Normal file
21
packages/mobile/src/router/index.js
Normal file
@ -0,0 +1,21 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Home',
|
||||
component: () => import('../views/Home.vue')
|
||||
},
|
||||
{
|
||||
path: '/user',
|
||||
name: 'User',
|
||||
component: () => import('../views/User.vue')
|
||||
}
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes
|
||||
})
|
||||
|
||||
export default router
|
||||
16
packages/mobile/src/styles/index.css
Normal file
16
packages/mobile/src/styles/index.css
Normal file
@ -0,0 +1,16 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
#app {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
}
|
||||
58
packages/mobile/src/views/Home.vue
Normal file
58
packages/mobile/src/views/Home.vue
Normal file
@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div class="home">
|
||||
<van-nav-bar title="首页" fixed placeholder />
|
||||
|
||||
<div class="content">
|
||||
<van-cell-group inset>
|
||||
<van-cell title="欢迎使用" value="H5移动端" />
|
||||
</van-cell-group>
|
||||
|
||||
<van-grid :column-num="2" class="grid">
|
||||
<van-grid-item icon="photo-o" text="功能1" @click="showToast('功能1')" />
|
||||
<van-grid-item icon="chat-o" text="功能2" @click="showToast('功能2')" />
|
||||
<van-grid-item icon="setting-o" text="功能3" @click="showToast('功能3')" />
|
||||
<van-grid-item icon="star-o" text="功能4" @click="showToast('功能4')" />
|
||||
</van-grid>
|
||||
|
||||
<van-button type="primary" block class="btn" @click="goToUser">
|
||||
进入个人中心
|
||||
</van-button>
|
||||
</div>
|
||||
|
||||
<van-tabbar v-model="active" route>
|
||||
<van-tabbar-item icon="home-o" to="/">首页</van-tabbar-item>
|
||||
<van-tabbar-item icon="user-o" to="/user">我的</van-tabbar-item>
|
||||
</van-tabbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { showToast } from 'vant'
|
||||
|
||||
const router = useRouter()
|
||||
const active = ref(0)
|
||||
|
||||
const goToUser = () => {
|
||||
router.push('/user')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.home {
|
||||
padding-bottom: 50px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.grid {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin-top: 24px;
|
||||
}
|
||||
</style>
|
||||
78
packages/mobile/src/views/User.vue
Normal file
78
packages/mobile/src/views/User.vue
Normal file
@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div class="user">
|
||||
<van-nav-bar title="我的" fixed placeholder />
|
||||
|
||||
<div class="user-header">
|
||||
<van-image
|
||||
round
|
||||
width="80"
|
||||
height="80"
|
||||
src="https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg"
|
||||
/>
|
||||
<div class="user-name">用户名</div>
|
||||
</div>
|
||||
|
||||
<van-cell-group inset class="menu-group">
|
||||
<van-cell title="个人信息" is-link @click="showToast('个人信息')" />
|
||||
<van-cell title="我的订单" is-link @click="showToast('我的订单')" />
|
||||
<van-cell title="收货地址" is-link @click="showToast('收货地址')" />
|
||||
<van-cell title="设置" is-link @click="showToast('设置')" />
|
||||
</van-cell-group>
|
||||
|
||||
<van-button type="danger" block class="logout-btn" @click="handleLogout">
|
||||
退出登录
|
||||
</van-button>
|
||||
|
||||
<van-tabbar v-model="active" route>
|
||||
<van-tabbar-item icon="home-o" to="/">首页</van-tabbar-item>
|
||||
<van-tabbar-item icon="user-o" to="/user">我的</van-tabbar-item>
|
||||
</van-tabbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { showToast, showDialog } from 'vant'
|
||||
|
||||
const router = useRouter()
|
||||
const active = ref(1)
|
||||
|
||||
const handleLogout = () => {
|
||||
showDialog({
|
||||
title: '提示',
|
||||
message: '确定要退出登录吗?'
|
||||
}).then(() => {
|
||||
showToast('已退出')
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.user {
|
||||
padding-bottom: 50px;
|
||||
}
|
||||
|
||||
.user-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 40px 0;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
margin-top: 16px;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #323233;
|
||||
}
|
||||
|
||||
.menu-group {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
margin: 24px 16px;
|
||||
}
|
||||
</style>
|
||||
30
packages/mobile/vite.config.js
Normal file
30
packages/mobile/vite.config.js
Normal file
@ -0,0 +1,30 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import { resolve } from 'path'
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
import { VantResolver } from 'unplugin-vue-components/resolvers'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
Components({
|
||||
resolvers: [VantResolver()]
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, 'src'),
|
||||
'@shared': resolve(__dirname, '../shared')
|
||||
}
|
||||
},
|
||||
server: {
|
||||
port: 8080,
|
||||
host: '0.0.0.0',
|
||||
open: true
|
||||
},
|
||||
build: {
|
||||
outDir: 'dist',
|
||||
assetsDir: 'assets',
|
||||
sourcemap: false
|
||||
}
|
||||
})
|
||||
12
packages/screen/index.html
Normal file
12
packages/screen/index.html
Normal file
@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>数据大屏</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
24
packages/screen/package.json
Normal file
24
packages/screen/package.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "@h5/screen",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.4.0",
|
||||
"vue-router": "^4.2.0",
|
||||
"pinia": "^2.1.0",
|
||||
"axios": "^1.6.0",
|
||||
"echarts": "^5.5.0",
|
||||
"vue-echarts": "^6.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.0.0",
|
||||
"vite": "^5.0.0",
|
||||
"sass": "^1.70.0"
|
||||
}
|
||||
}
|
||||
19
packages/screen/src/App.vue
Normal file
19
packages/screen/src/App.vue
Normal file
@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'App'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
12
packages/screen/src/main.js
Normal file
12
packages/screen/src/main.js
Normal file
@ -0,0 +1,12 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import router from './router'
|
||||
import App from './App.vue'
|
||||
import './styles/index.scss'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
|
||||
app.mount('#app')
|
||||
16
packages/screen/src/router/index.js
Normal file
16
packages/screen/src/router/index.js
Normal file
@ -0,0 +1,16 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Home',
|
||||
component: () => import('../views/Home.vue')
|
||||
}
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes
|
||||
})
|
||||
|
||||
export default router
|
||||
16
packages/screen/src/styles/index.scss
Normal file
16
packages/screen/src/styles/index.scss
Normal file
@ -0,0 +1,16 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Microsoft YaHei', Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
}
|
||||
102
packages/screen/src/views/Home.vue
Normal file
102
packages/screen/src/views/Home.vue
Normal file
@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<div class="screen-container">
|
||||
<header class="screen-header">
|
||||
<h1>数据大屏</h1>
|
||||
</header>
|
||||
|
||||
<div class="screen-content">
|
||||
<div class="screen-left">
|
||||
<div class="chart-box">
|
||||
<h3>左侧图表1</h3>
|
||||
</div>
|
||||
<div class="chart-box">
|
||||
<h3>左侧图表2</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="screen-center">
|
||||
<div class="chart-box center-main">
|
||||
<h3>中间主图表</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="screen-right">
|
||||
<div class="chart-box">
|
||||
<h3>右侧图表1</h3>
|
||||
</div>
|
||||
<div class="chart-box">
|
||||
<h3>右侧图表2</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Home',
|
||||
setup() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.screen-container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background: #0a1e3e;
|
||||
color: #fff;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.screen-header {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
|
||||
h1 {
|
||||
font-size: 36px;
|
||||
font-weight: bold;
|
||||
background: linear-gradient(to right, #4facfe, #00f2fe);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.screen-content {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
height: calc(100% - 80px);
|
||||
}
|
||||
|
||||
.screen-left,
|
||||
.screen-right {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.screen-center {
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.chart-box {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
flex: 1;
|
||||
|
||||
h3 {
|
||||
margin: 0 0 15px 0;
|
||||
font-size: 18px;
|
||||
color: #4facfe;
|
||||
}
|
||||
|
||||
&.center-main {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
31
packages/screen/vite.config.js
Normal file
31
packages/screen/vite.config.js
Normal file
@ -0,0 +1,31 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import { resolve } from 'path'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, 'src'),
|
||||
'@shared': resolve(__dirname, '../shared')
|
||||
}
|
||||
},
|
||||
server: {
|
||||
port: 3000,
|
||||
open: true,
|
||||
cors: true
|
||||
},
|
||||
build: {
|
||||
outDir: 'dist',
|
||||
assetsDir: 'assets',
|
||||
sourcemap: false,
|
||||
minify: 'terser',
|
||||
rollupOptions: {
|
||||
output: {
|
||||
chunkFileNames: 'js/[name]-[hash].js',
|
||||
entryFileNames: 'js/[name]-[hash].js',
|
||||
assetFileNames: '[ext]/[name]-[hash].[ext]'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
17
packages/shared/api/index.js
Normal file
17
packages/shared/api/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
import request from './request'
|
||||
|
||||
// 示例API接口
|
||||
export const getUserInfo = () => {
|
||||
return request({
|
||||
url: '/user/info',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export const getDataList = (params) => {
|
||||
return request({
|
||||
url: '/data/list',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
42
packages/shared/api/request.js
Normal file
42
packages/shared/api/request.js
Normal file
@ -0,0 +1,42 @@
|
||||
import axios from 'axios'
|
||||
|
||||
// 创建axios实例
|
||||
const request = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
|
||||
timeout: 10000
|
||||
})
|
||||
|
||||
// 请求拦截器
|
||||
request.interceptors.request.use(
|
||||
config => {
|
||||
// 可以在这里添加token等
|
||||
const token = localStorage.getItem('token')
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
return config
|
||||
},
|
||||
error => {
|
||||
console.error('请求错误:', error)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// 响应拦截器
|
||||
request.interceptors.response.use(
|
||||
response => {
|
||||
const res = response.data
|
||||
// 根据实际情况处理响应
|
||||
if (res.code !== 200) {
|
||||
console.error('接口错误:', res.message)
|
||||
return Promise.reject(new Error(res.message || 'Error'))
|
||||
}
|
||||
return res
|
||||
},
|
||||
error => {
|
||||
console.error('响应错误:', error)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
export default request
|
||||
18
packages/shared/config/index.js
Normal file
18
packages/shared/config/index.js
Normal file
@ -0,0 +1,18 @@
|
||||
// 环境配置
|
||||
export const ENV = import.meta.env.MODE || 'development'
|
||||
|
||||
// API配置
|
||||
export const API_CONFIG = {
|
||||
development: {
|
||||
baseURL: 'http://localhost:3000/api'
|
||||
},
|
||||
production: {
|
||||
baseURL: 'https://api.example.com'
|
||||
}
|
||||
}
|
||||
|
||||
// 应用配置
|
||||
export const APP_CONFIG = {
|
||||
title: '数据大屏',
|
||||
version: '1.0.0'
|
||||
}
|
||||
3
packages/shared/index.js
Normal file
3
packages/shared/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './api'
|
||||
export * from './utils'
|
||||
export * from './config'
|
||||
11
packages/shared/package.json
Normal file
11
packages/shared/package.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "@h5/shared",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "index.js",
|
||||
"scripts": {},
|
||||
"dependencies": {
|
||||
"axios": "^1.6.0"
|
||||
}
|
||||
}
|
||||
75
packages/shared/utils/index.js
Normal file
75
packages/shared/utils/index.js
Normal file
@ -0,0 +1,75 @@
|
||||
/**
|
||||
* 格式化日期
|
||||
* @param {Date|string|number} date
|
||||
* @param {string} format
|
||||
* @returns {string}
|
||||
*/
|
||||
export function formatDate(date, format = 'YYYY-MM-DD HH:mm:ss') {
|
||||
const d = new Date(date)
|
||||
const year = d.getFullYear()
|
||||
const month = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
const hours = String(d.getHours()).padStart(2, '0')
|
||||
const minutes = String(d.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(d.getSeconds()).padStart(2, '0')
|
||||
|
||||
return format
|
||||
.replace('YYYY', year)
|
||||
.replace('MM', month)
|
||||
.replace('DD', day)
|
||||
.replace('HH', hours)
|
||||
.replace('mm', minutes)
|
||||
.replace('ss', seconds)
|
||||
}
|
||||
|
||||
/**
|
||||
* 防抖函数
|
||||
* @param {Function} fn
|
||||
* @param {number} delay
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function debounce(fn, delay = 300) {
|
||||
let timer = null
|
||||
return function(...args) {
|
||||
if (timer) clearTimeout(timer)
|
||||
timer = setTimeout(() => {
|
||||
fn.apply(this, args)
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 节流函数
|
||||
* @param {Function} fn
|
||||
* @param {number} delay
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function throttle(fn, delay = 300) {
|
||||
let last = 0
|
||||
return function(...args) {
|
||||
const now = Date.now()
|
||||
if (now - last >= delay) {
|
||||
last = now
|
||||
fn.apply(this, args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 深拷贝
|
||||
* @param {any} obj
|
||||
* @returns {any}
|
||||
*/
|
||||
export function deepClone(obj) {
|
||||
if (obj === null || typeof obj !== 'object') return obj
|
||||
if (obj instanceof Date) return new Date(obj)
|
||||
if (obj instanceof Array) return obj.map(item => deepClone(item))
|
||||
|
||||
const clonedObj = {}
|
||||
for (const key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
clonedObj[key] = deepClone(obj[key])
|
||||
}
|
||||
}
|
||||
return clonedObj
|
||||
}
|
||||
1867
pnpm-lock.yaml
generated
Normal file
1867
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
2
pnpm-workspace.yaml
Normal file
2
pnpm-workspace.yaml
Normal file
@ -0,0 +1,2 @@
|
||||
packages:
|
||||
- 'packages/*'
|
||||
Loading…
x
Reference in New Issue
Block a user