feat: initialize independent Sense product skeleton (#21)
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<section class="page-container">
|
||||
<div class="page-heading">
|
||||
<div>
|
||||
<h1>工作台</h1>
|
||||
<p>查看设备、视频接入和服务状态,按提示处理异常。</p>
|
||||
</div>
|
||||
<el-tag type="success" effect="light">Sense 独立运行</el-tag>
|
||||
</div>
|
||||
|
||||
<el-row :gutter="16">
|
||||
<el-col v-for="item in overview" :key="item.label" :xs="24" :sm="12" :lg="6">
|
||||
<el-card shadow="never" class="overview-card">
|
||||
<span>{{ item.label }}</span>
|
||||
<strong>{{ item.value }}</strong>
|
||||
<small>{{ item.description }}</small>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-card shadow="never" class="next-step-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>开始使用</span>
|
||||
<el-tag type="info">按步骤配置</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<el-steps :active="0" finish-status="success" simple>
|
||||
<el-step title="添加设备" />
|
||||
<el-step title="检查视频" />
|
||||
<el-step title="配置区域" />
|
||||
</el-steps>
|
||||
<el-empty description="功能模块会按工单逐步启用,不显示框架演示页面。" :image-size="92" />
|
||||
</el-card>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const overview = [
|
||||
{ label: '设备总数', value: '—', description: '添加设备后显示' },
|
||||
{ label: '视频正常', value: '—', description: '接入验证后显示' },
|
||||
{ label: '需要处理', value: '—', description: '异常状态集中提示' },
|
||||
{ label: '区域配置', value: '—', description: '发布后显示版本' }
|
||||
]
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.overview-card { margin-bottom: 16px; }
|
||||
.overview-card :deep(.el-card__body) { display: flex; flex-direction: column; gap: 8px; }
|
||||
.overview-card strong { color: #1d2129; font-size: 28px; }
|
||||
.overview-card small { color: #86909c; }
|
||||
.next-step-card { margin-top: 4px; }
|
||||
.card-header { display: flex; align-items: center; justify-content: space-between; }
|
||||
</style>
|
||||
|
||||
@@ -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">
|
||||
<meta name="color-scheme" content="light">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>Sense 管理端需要启用 JavaScript。</noscript>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { createApp } from 'vue'
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import store from './store'
|
||||
import './theme.css'
|
||||
|
||||
const app = createApp(App)
|
||||
Object.entries(ElementPlusIconsVue).forEach(([name, component]) => {
|
||||
app.component(name, component)
|
||||
})
|
||||
app.use(ElementPlus)
|
||||
app.use(store)
|
||||
app.use(router)
|
||||
app.mount('#app')
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import axios from 'axios'
|
||||
|
||||
const request = axios.create({
|
||||
baseURL: '/api/v1',
|
||||
timeout: 15000,
|
||||
withCredentials: true,
|
||||
headers: { 'X-Product': 'sense' }
|
||||
})
|
||||
|
||||
request.interceptors.response.use(
|
||||
(response) => response.data,
|
||||
(error) => Promise.reject(error.response?.data?.error || error)
|
||||
)
|
||||
|
||||
export default request
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
|
||||
import AppLayout from '../layout/AppLayout.vue'
|
||||
import Dashboard from './Dashboard.vue'
|
||||
|
||||
const featureContext = require.context('../', true, /router\/modules\/sense-.*\.js$/)
|
||||
const featureRoutes = featureContext.keys().flatMap((key) => {
|
||||
const module = featureContext(key).default
|
||||
return Array.isArray(module) ? module : [module]
|
||||
})
|
||||
|
||||
const publicRoutes = featureRoutes.filter((route) => route.meta?.public)
|
||||
const protectedRoutes = featureRoutes.filter((route) => !route.meta?.public)
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: [
|
||||
...publicRoutes,
|
||||
{
|
||||
path: '/',
|
||||
component: AppLayout,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'sense-dashboard',
|
||||
component: Dashboard,
|
||||
meta: { title: '工作台', icon: 'Odometer', order: 0 }
|
||||
},
|
||||
...protectedRoutes
|
||||
]
|
||||
},
|
||||
{ path: '/:pathMatch(.*)*', redirect: '/' }
|
||||
]
|
||||
})
|
||||
|
||||
router.beforeEach((to) => {
|
||||
if (to.meta.public) return true
|
||||
const session = window.sessionStorage.getItem('sense_session')
|
||||
const hasIdentityModule = router.hasRoute('sense-login')
|
||||
if (hasIdentityModule && !session) return { name: 'sense-login', query: { redirect: to.fullPath } }
|
||||
return true
|
||||
})
|
||||
|
||||
export default router
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { createStore } from 'vuex'
|
||||
|
||||
const moduleContext = require.context('../', true, /store\/modules\/sense-.*\.js$/)
|
||||
const modules = {}
|
||||
moduleContext.keys().forEach((key) => {
|
||||
const name = key.split('/').pop().replace('.js', '')
|
||||
modules[name] = moduleContext(key).default
|
||||
})
|
||||
|
||||
export default createStore({ modules })
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
:root {
|
||||
--sense-bg: #f2f3f5;
|
||||
--sense-sidebar: #001529;
|
||||
--sense-primary: #1890ff;
|
||||
--sense-text: #1d2129;
|
||||
--sense-muted: #86909c;
|
||||
--sense-border: #e5e6eb;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
html, body, #app { min-width: 1080px; min-height: 100%; margin: 0; }
|
||||
body {
|
||||
background: var(--sense-bg);
|
||||
color: var(--sense-text);
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei", sans-serif;
|
||||
}
|
||||
.page-container { padding: 20px; }
|
||||
.page-heading { display: flex; align-items: center; justify-content: space-between; margin-bottom: 18px; }
|
||||
.page-heading h1 { margin: 0 0 6px; font-size: 22px; font-weight: 600; }
|
||||
.page-heading p { margin: 0; color: var(--sense-muted); font-size: 14px; }
|
||||
.search-toolbar { margin-bottom: 16px; padding: 16px 16px 0; background: #fff; border: 1px solid var(--sense-border); border-radius: 4px; }
|
||||
.table-container { padding: 16px; background: #fff; border: 1px solid var(--sense-border); border-radius: 4px; }
|
||||
.table-actions { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
|
||||
.pagination { display: flex; justify-content: flex-end; margin-top: 16px; }
|
||||
|
||||
Reference in New Issue
Block a user