feat: 添加过程矩阵全屏功能并优化部署配置
添加过程矩阵全屏查看功能,包括状态管理、快捷键支持和响应式布局 优化 Dockerfile 使用 npm ci 并添加生产环境标志 添加 nginx 配置支持 SPA 路由和静态资源缓存
This commit is contained in:
@@ -4,7 +4,7 @@ WORKDIR /app
|
|||||||
|
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
|
|
||||||
RUN npm install
|
RUN npm ci --only=production=false
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
@@ -12,6 +12,10 @@ RUN npm run build
|
|||||||
|
|
||||||
FROM nginx:alpine
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
# 复制自定义 nginx 配置(支持 SPA 路由)
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
# 复制构建产物
|
||||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|||||||
22
nginx.conf
Normal file
22
nginx.conf
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name localhost;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
# SPA 路由支持 - 所有路径都返回 index.html
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 静态资源缓存
|
||||||
|
location /assets {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
}
|
||||||
|
|
||||||
|
# gzip 压缩
|
||||||
|
gzip on;
|
||||||
|
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
|
||||||
|
gzip_min_length 1000;
|
||||||
|
}
|
||||||
@@ -4,13 +4,18 @@
|
|||||||
*/
|
*/
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { motion } from 'framer-motion'
|
import { motion } from 'framer-motion'
|
||||||
|
import { clsx } from 'clsx'
|
||||||
import {
|
import {
|
||||||
knowledgeAreas,
|
knowledgeAreas,
|
||||||
processGroups,
|
processGroups,
|
||||||
processes,
|
processes,
|
||||||
} from '@/data'
|
} from '@/data'
|
||||||
|
|
||||||
export function ProcessMatrix() {
|
interface ProcessMatrixProps {
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ProcessMatrix({ className }: ProcessMatrixProps) {
|
||||||
// 构建矩阵数据:knowledgeAreaId -> processGroupId -> Process[]
|
// 构建矩阵数据:knowledgeAreaId -> processGroupId -> Process[]
|
||||||
const matrix = new Map<string, Map<string, typeof processes>>()
|
const matrix = new Map<string, Map<string, typeof processes>>()
|
||||||
|
|
||||||
@@ -33,7 +38,7 @@ export function ProcessMatrix() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="overflow-x-auto">
|
<div className={clsx("overflow-x-auto", className)}>
|
||||||
<table className="w-full border-collapse min-w-[900px]">
|
<table className="w-full border-collapse min-w-[900px]">
|
||||||
{/* 表头:过程组 */}
|
{/* 表头:过程组 */}
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -1,19 +1,89 @@
|
|||||||
/**
|
/**
|
||||||
* 49过程矩阵页面
|
* 49过程矩阵页面
|
||||||
*/
|
*/
|
||||||
|
import { useEffect } from 'react'
|
||||||
import { ProcessMatrix } from '@/components/visualize'
|
import { ProcessMatrix } from '@/components/visualize'
|
||||||
|
import { Maximize2, Minimize2 } from 'lucide-react'
|
||||||
|
import { useAppStore } from '@/stores/useAppStore'
|
||||||
|
import { clsx } from 'clsx'
|
||||||
|
|
||||||
export function ProcessMatrixPage() {
|
export function ProcessMatrixPage() {
|
||||||
|
const isFullScreen = useAppStore((s) => s.matrixFullScreen)
|
||||||
|
const setMatrixFullScreen = useAppStore((s) => s.setMatrixFullScreen)
|
||||||
|
const setSidebarOpen = useAppStore((s) => s.setSidebarOpen)
|
||||||
|
|
||||||
|
const toggleFullScreen = () => {
|
||||||
|
if (!isFullScreen) {
|
||||||
|
setSidebarOpen(false)
|
||||||
|
}
|
||||||
|
setMatrixFullScreen(!isFullScreen)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Escape key to exit full screen
|
||||||
|
useEffect(() => {
|
||||||
|
const handleEsc = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Escape' && isFullScreen) {
|
||||||
|
setMatrixFullScreen(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.addEventListener('keydown', handleEsc)
|
||||||
|
return () => window.removeEventListener('keydown', handleEsc)
|
||||||
|
}, [isFullScreen, setMatrixFullScreen])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className={clsx("space-y-6", isFullScreen && "fixed inset-0 z-50 bg-white dark:bg-gray-900 p-0 m-0 space-y-0")}>
|
||||||
<div>
|
{/* 隐藏滚动条的样式 */}
|
||||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">49过程矩阵</h1>
|
{isFullScreen && (
|
||||||
<p className="text-gray-500 dark:text-gray-400 mt-1">
|
<style>{`
|
||||||
知识领域 × 过程组 的全景矩阵视图,点击过程可查看详情
|
.no-scrollbar::-webkit-scrollbar {
|
||||||
</p>
|
display: none;
|
||||||
</div>
|
}
|
||||||
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 p-4 overflow-hidden">
|
.no-scrollbar {
|
||||||
<ProcessMatrix />
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isFullScreen && (
|
||||||
|
<div className="flex justify-between items-end">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">49过程矩阵</h1>
|
||||||
|
<p className="text-gray-500 dark:text-gray-400 mt-1">
|
||||||
|
知识领域 × 过程组 的全景矩阵视图,点击过程可查看详情
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={toggleFullScreen}
|
||||||
|
className="flex items-center gap-2 px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors shadow-sm"
|
||||||
|
>
|
||||||
|
<Maximize2 className="w-4 h-4" />
|
||||||
|
全屏查看
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={clsx(
|
||||||
|
"bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 overflow-hidden transition-all duration-300",
|
||||||
|
!isFullScreen && "p-4",
|
||||||
|
isFullScreen && "h-full w-full border-0 rounded-none shadow-none flex flex-col"
|
||||||
|
)}>
|
||||||
|
{isFullScreen && (
|
||||||
|
<div className="flex justify-between items-center px-4 py-2 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 shrink-0">
|
||||||
|
<span className="font-medium text-gray-900 dark:text-white">49过程矩阵全景图</span>
|
||||||
|
<button
|
||||||
|
onClick={toggleFullScreen}
|
||||||
|
className="flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors"
|
||||||
|
>
|
||||||
|
<Minimize2 className="w-4 h-4" />
|
||||||
|
退出全屏
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={clsx("relative", isFullScreen ? "flex-1 overflow-hidden" : "")}>
|
||||||
|
<ProcessMatrix className={clsx(isFullScreen && "h-full w-full overflow-auto no-scrollbar")} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ interface AppState {
|
|||||||
sidebarOpen: boolean
|
sidebarOpen: boolean
|
||||||
darkMode: boolean
|
darkMode: boolean
|
||||||
searchQuery: string
|
searchQuery: string
|
||||||
|
matrixFullScreen: boolean
|
||||||
|
|
||||||
// 操作
|
// 操作
|
||||||
toggleSidebar: () => void
|
toggleSidebar: () => void
|
||||||
@@ -13,6 +14,7 @@ interface AppState {
|
|||||||
toggleDarkMode: () => void
|
toggleDarkMode: () => void
|
||||||
setDarkMode: (dark: boolean) => void
|
setDarkMode: (dark: boolean) => void
|
||||||
setSearchQuery: (query: string) => void
|
setSearchQuery: (query: string) => void
|
||||||
|
setMatrixFullScreen: (fullScreen: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useAppStore = create<AppState>()(
|
export const useAppStore = create<AppState>()(
|
||||||
@@ -22,6 +24,7 @@ export const useAppStore = create<AppState>()(
|
|||||||
sidebarOpen: true,
|
sidebarOpen: true,
|
||||||
darkMode: false,
|
darkMode: false,
|
||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
|
matrixFullScreen: false,
|
||||||
|
|
||||||
// 操作方法
|
// 操作方法
|
||||||
toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
|
toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
|
||||||
@@ -29,12 +32,14 @@ export const useAppStore = create<AppState>()(
|
|||||||
toggleDarkMode: () => set((state) => ({ darkMode: !state.darkMode })),
|
toggleDarkMode: () => set((state) => ({ darkMode: !state.darkMode })),
|
||||||
setDarkMode: (dark) => set({ darkMode: dark }),
|
setDarkMode: (dark) => set({ darkMode: dark }),
|
||||||
setSearchQuery: (query) => set({ searchQuery: query }),
|
setSearchQuery: (query) => set({ searchQuery: query }),
|
||||||
|
setMatrixFullScreen: (fullScreen) => set({ matrixFullScreen: fullScreen }),
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: 'ittoview-app-storage',
|
name: 'ittoview-app-storage',
|
||||||
partialize: (state) => ({
|
partialize: (state) => ({
|
||||||
sidebarOpen: state.sidebarOpen,
|
sidebarOpen: state.sidebarOpen,
|
||||||
darkMode: state.darkMode,
|
darkMode: state.darkMode,
|
||||||
|
matrixFullScreen: state.matrixFullScreen,
|
||||||
// searchQuery 不持久化到 localStorage,刷新后重置
|
// searchQuery 不持久化到 localStorage,刷新后重置
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user