2
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"name": "设计中心",
|
||||
"description": "展示已添加的设计组件实例"
|
||||
"description": "实时预览选中的Vue页面或设计组件"
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { defineAsyncComponent, markRaw } from 'vue'
|
||||
import { defineAsyncComponent, markRaw, computed, watch } from 'vue'
|
||||
import { useDesignStore } from '../../stores/designStore'
|
||||
import { useVueFileStore } from '../../stores/vueFileStore'
|
||||
import config from './index.json'
|
||||
|
||||
const designStore = useDesignStore()
|
||||
const vueFileStore = useVueFileStore()
|
||||
|
||||
// 自动扫描所有设计组件(eager 模式确保同步加载)
|
||||
const designComponentModules = import.meta.glob('../../designComponents/*/index.vue', { eager: true })
|
||||
@@ -33,39 +35,78 @@ const handleRemove = (instanceId: string, event: Event) => {
|
||||
event.stopPropagation()
|
||||
designStore.removeComponent(instanceId)
|
||||
}
|
||||
|
||||
// 扫描所有views目录下的Vue文件
|
||||
const viewModules = import.meta.glob('../../../views/**/*.vue')
|
||||
|
||||
// 当前选中的Vue页面组件
|
||||
const selectedPageComponent = computed(() => {
|
||||
if (!vueFileStore.selectedFilePath) {
|
||||
return null
|
||||
}
|
||||
|
||||
// 动态加载选中的组件
|
||||
const loader = viewModules[vueFileStore.selectedFilePath]
|
||||
if (loader) {
|
||||
return defineAsyncComponent(loader as any)
|
||||
}
|
||||
|
||||
return null
|
||||
})
|
||||
|
||||
// 监听选中文件变化
|
||||
watch(() => vueFileStore.selectedFilePath, (newPath) => {
|
||||
console.log('设计中心:选中的文件路径变化', newPath)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="design-center">
|
||||
<div class="center-header">
|
||||
<span class="title">{{ config.name }}</span>
|
||||
<span class="count">{{ designStore.components.length }} 个实例</span>
|
||||
<span class="count" v-if="!vueFileStore.selectedFilePath">
|
||||
{{ designStore.components.length }} 个实例
|
||||
</span>
|
||||
<span class="file-info" v-else>
|
||||
📄 {{ vueFileStore.selectedFileName }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="center-body">
|
||||
<div
|
||||
v-for="instance in designStore.components"
|
||||
:key="instance.id"
|
||||
class="component-row"
|
||||
:class="{ selected: designStore.selectedId === instance.id }"
|
||||
@click="handleSelect(instance.id)"
|
||||
>
|
||||
<div class="component-label">
|
||||
<span class="component-name">{{ instance.name }}</span>
|
||||
<button class="remove-btn" @click="handleRemove(instance.id, $event)">×</button>
|
||||
</div>
|
||||
<div class="component-preview">
|
||||
<component
|
||||
v-if="getComponent(instance.componentId)"
|
||||
:is="getComponent(instance.componentId)"
|
||||
v-bind="instance.props"
|
||||
/>
|
||||
<!-- 动态渲染选中的Vue页面 -->
|
||||
<div v-if="selectedPageComponent" class="page-preview">
|
||||
<component :is="selectedPageComponent" />
|
||||
</div>
|
||||
|
||||
<!-- 原有的设计组件实例列表 -->
|
||||
<div v-else-if="designStore.components.length > 0" class="component-list">
|
||||
<div
|
||||
v-for="instance in designStore.components"
|
||||
:key="instance.id"
|
||||
class="component-row"
|
||||
:class="{ selected: designStore.selectedId === instance.id }"
|
||||
@click="handleSelect(instance.id)"
|
||||
>
|
||||
<div class="component-label">
|
||||
<span class="component-name">{{ instance.name }}</span>
|
||||
<button class="remove-btn" @click="handleRemove(instance.id, $event)">×</button>
|
||||
</div>
|
||||
<div class="component-preview">
|
||||
<component
|
||||
v-if="getComponent(instance.componentId)"
|
||||
:is="getComponent(instance.componentId)"
|
||||
v-bind="instance.props"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="designStore.components.length === 0" class="empty-tip">
|
||||
<!-- 空状态 -->
|
||||
<div v-else class="empty-tip">
|
||||
<div class="empty-icon">🎨</div>
|
||||
<div>暂无设计组件</div>
|
||||
<div class="empty-hint">从左侧列表点击添加</div>
|
||||
<div>暂无内容</div>
|
||||
<div class="empty-hint">
|
||||
点击“页面管理”选择Vue页面,或从左侧列表添加设计组件
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -99,12 +140,32 @@ const handleRemove = (instanceId: string, event: Event) => {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.file-info {
|
||||
color: #4fc3f7;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.center-body {
|
||||
flex: 1;
|
||||
padding: 12px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.page-preview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.component-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.component-row {
|
||||
margin-bottom: 12px;
|
||||
border-radius: 6px;
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "页面管理",
|
||||
"description": "浏览和管理src/views目录下的Vue页面文件"
|
||||
}
|
||||
254
draggable-panels/src/fauto/materials/PageManager/index.vue
Normal file
254
draggable-panels/src/fauto/materials/PageManager/index.vue
Normal file
@@ -0,0 +1,254 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useVueFileStore, type VueFileNode } from '../../stores/vueFileStore'
|
||||
import config from './index.json'
|
||||
|
||||
const vueFileStore = useVueFileStore()
|
||||
|
||||
// 扩展的节点数据(用于el-tree)
|
||||
interface TreeNodeData {
|
||||
label: string
|
||||
path: string
|
||||
type: 'file' | 'folder'
|
||||
children?: TreeNodeData[]
|
||||
}
|
||||
|
||||
const treeData = ref<TreeNodeData[]>([])
|
||||
const defaultExpandedKeys = ref<string[]>([])
|
||||
|
||||
// 扫描views目录下的所有Vue文件
|
||||
const scanViewsDirectory = () => {
|
||||
// 使用Vite的import.meta.glob扫描views目录
|
||||
const viewModules = import.meta.glob('../../../views/**/*.vue')
|
||||
|
||||
console.log('扫描到的文件:', Object.keys(viewModules))
|
||||
|
||||
// 构建文件树
|
||||
const fileTree = vueFileStore.buildFileTree(viewModules)
|
||||
|
||||
// 转换为el-tree需要的格式
|
||||
const convertToTreeData = (nodes: VueFileNode[]): TreeNodeData[] => {
|
||||
return nodes.map(node => ({
|
||||
label: node.name,
|
||||
path: node.path,
|
||||
type: node.type,
|
||||
children: node.children ? convertToTreeData(node.children) : undefined
|
||||
}))
|
||||
}
|
||||
|
||||
treeData.value = convertToTreeData(fileTree)
|
||||
|
||||
// 默认展开所有文件夹
|
||||
defaultExpandedKeys.value = getAllFolderPaths(treeData.value)
|
||||
}
|
||||
|
||||
// 获取所有文件夹路径(用于默认展开)
|
||||
const getAllFolderPaths = (nodes: TreeNodeData[]): string[] => {
|
||||
const paths: string[] = []
|
||||
|
||||
const traverse = (items: TreeNodeData[]) => {
|
||||
items.forEach(item => {
|
||||
if (item.type === 'folder') {
|
||||
paths.push(item.path)
|
||||
if (item.children) {
|
||||
traverse(item.children)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
traverse(nodes)
|
||||
return paths
|
||||
}
|
||||
|
||||
// 处理节点点击事件
|
||||
const handleNodeClick = (data: TreeNodeData) => {
|
||||
if (data.type === 'file') {
|
||||
// 只有文件才能被选中
|
||||
vueFileStore.selectFile(data.path, data.label)
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义树节点图标
|
||||
const getNodeIcon = (data: TreeNodeData) => {
|
||||
if (data.type === 'folder') {
|
||||
return '📁'
|
||||
}
|
||||
return '📄'
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
scanViewsDirectory()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-manager">
|
||||
<div class="manager-header">
|
||||
<span class="title">{{ config.name }}</span>
|
||||
<span class="count">{{ treeData.length }} 项</span>
|
||||
</div>
|
||||
<div class="manager-body">
|
||||
<div class="current-file" v-if="vueFileStore.selectedFileName">
|
||||
<div class="label">当前文件:</div>
|
||||
<div class="file-name">{{ vueFileStore.selectedFileName }}</div>
|
||||
</div>
|
||||
|
||||
<div class="tree-container">
|
||||
<el-tree
|
||||
:data="treeData"
|
||||
:props="{ label: 'label', children: 'children' }"
|
||||
:default-expanded-keys="defaultExpandedKeys"
|
||||
node-key="path"
|
||||
highlight-current
|
||||
@node-click="handleNodeClick"
|
||||
>
|
||||
<template #default="{ node, data }">
|
||||
<div class="custom-tree-node">
|
||||
<span class="node-icon">{{ getNodeIcon(data) }}</span>
|
||||
<span
|
||||
class="node-label"
|
||||
:class="{
|
||||
'is-file': data.type === 'file',
|
||||
'is-selected': data.path === vueFileStore.selectedFilePath
|
||||
}"
|
||||
>
|
||||
{{ node.label }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-tree>
|
||||
|
||||
<div v-if="treeData.length === 0" class="empty-tip">
|
||||
<div class="empty-icon">📂</div>
|
||||
<div>暂无Vue文件</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.page-manager {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background: #1e1e1e;
|
||||
}
|
||||
|
||||
.manager-header {
|
||||
padding: 8px 12px;
|
||||
background: #2d2d2d;
|
||||
border-bottom: 1px solid #3c3c3c;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #cccccc;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.count {
|
||||
color: #888888;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.manager-body {
|
||||
flex: 1;
|
||||
padding: 12px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.current-file {
|
||||
margin-bottom: 12px;
|
||||
padding: 8px 12px;
|
||||
background: #094771;
|
||||
border-radius: 4px;
|
||||
border-left: 3px solid #007acc;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #888888;
|
||||
font-size: 11px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
color: #e0e0e0;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tree-container {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* Element Plus Tree 样式覆盖 */
|
||||
:deep(.el-tree) {
|
||||
background: transparent;
|
||||
color: #cccccc;
|
||||
}
|
||||
|
||||
:deep(.el-tree-node__content) {
|
||||
background: transparent;
|
||||
height: 32px;
|
||||
padding: 0 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
:deep(.el-tree-node__content:hover) {
|
||||
background: #2a2d2e;
|
||||
}
|
||||
|
||||
:deep(.el-tree-node.is-current > .el-tree-node__content) {
|
||||
background: #094771;
|
||||
}
|
||||
|
||||
:deep(.el-tree-node__expand-icon) {
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
.custom-tree-node {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.node-icon {
|
||||
margin-right: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.node-label {
|
||||
flex: 1;
|
||||
color: #cccccc;
|
||||
}
|
||||
|
||||
.node-label.is-file {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.node-label.is-file:hover {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.node-label.is-selected {
|
||||
color: #4fc3f7;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
color: #666666;
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 40px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
</style>
|
||||
110
draggable-panels/src/fauto/stores/vueFileStore.ts
Normal file
110
draggable-panels/src/fauto/stores/vueFileStore.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
// Vue文件树节点
|
||||
export interface VueFileNode {
|
||||
name: string // 文件/文件夹名称
|
||||
path: string // 完整路径
|
||||
type: 'file' | 'folder' // 类型
|
||||
children?: VueFileNode[] // 子节点
|
||||
}
|
||||
|
||||
export const useVueFileStore = defineStore('vueFile', () => {
|
||||
// 当前选中的Vue文件路径
|
||||
const selectedFilePath = ref<string | null>(null)
|
||||
|
||||
// 当前选中的文件名
|
||||
const selectedFileName = ref<string | null>(null)
|
||||
|
||||
// 文件树结构
|
||||
const fileTree = ref<VueFileNode[]>([])
|
||||
|
||||
// 设置选中的文件
|
||||
const selectFile = (path: string, name: string) => {
|
||||
selectedFilePath.value = path
|
||||
selectedFileName.value = name
|
||||
console.log('选中文件:', { path, name })
|
||||
}
|
||||
|
||||
// 清除选中
|
||||
const clearSelection = () => {
|
||||
selectedFilePath.value = null
|
||||
selectedFileName.value = null
|
||||
}
|
||||
|
||||
// 构建文件树(从import.meta.glob结果)
|
||||
const buildFileTree = (modules: Record<string, any>): VueFileNode[] => {
|
||||
const tree: VueFileNode[] = []
|
||||
const folderMap = new Map<string, VueFileNode>()
|
||||
|
||||
for (const path in modules) {
|
||||
// 移除开头的 '../views/' 或 './views/'
|
||||
const relativePath = path.replace(/^\.\.?\/views\//, '')
|
||||
const parts = relativePath.split('/')
|
||||
|
||||
let currentLevel = tree
|
||||
let currentPath = ''
|
||||
|
||||
// 处理路径的每一部分
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const part = parts[i]
|
||||
currentPath = currentPath ? `${currentPath}/${part}` : part
|
||||
|
||||
const isFile = i === parts.length - 1 && part.endsWith('.vue')
|
||||
|
||||
if (isFile) {
|
||||
// 添加文件节点
|
||||
currentLevel.push({
|
||||
name: part,
|
||||
path: path,
|
||||
type: 'file'
|
||||
})
|
||||
} else {
|
||||
// 添加文件夹节点
|
||||
let folder = folderMap.get(currentPath)
|
||||
|
||||
if (!folder) {
|
||||
folder = {
|
||||
name: part,
|
||||
path: currentPath,
|
||||
type: 'folder',
|
||||
children: []
|
||||
}
|
||||
currentLevel.push(folder)
|
||||
folderMap.set(currentPath, folder)
|
||||
}
|
||||
|
||||
currentLevel = folder.children!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 排序:文件夹在前,文件在后
|
||||
const sortTree = (nodes: VueFileNode[]) => {
|
||||
nodes.sort((a, b) => {
|
||||
if (a.type !== b.type) {
|
||||
return a.type === 'folder' ? -1 : 1
|
||||
}
|
||||
return a.name.localeCompare(b.name)
|
||||
})
|
||||
|
||||
nodes.forEach(node => {
|
||||
if (node.children) {
|
||||
sortTree(node.children)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
sortTree(tree)
|
||||
return tree
|
||||
}
|
||||
|
||||
return {
|
||||
selectedFilePath,
|
||||
selectedFileName,
|
||||
fileTree,
|
||||
selectFile,
|
||||
clearSelection,
|
||||
buildFileTree
|
||||
}
|
||||
})
|
||||
@@ -1,5 +1,7 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import './style.css'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
@@ -9,4 +11,5 @@ const pinia = createPinia()
|
||||
|
||||
app.use(pinia)
|
||||
app.use(router)
|
||||
app.use(ElementPlus)
|
||||
app.mount('#app')
|
||||
|
||||
52
draggable-panels/src/views/TestPage1.vue
Normal file
52
draggable-panels/src/views/TestPage1.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<div class="test-page">
|
||||
<h2>测试页面 1</h2>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<div class="grid-content">左侧内容区域</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="grid-content">右侧内容区域</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20" style="margin-top: 20px;">
|
||||
<el-col :span="8">
|
||||
<div class="grid-content">列 1</div>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="grid-content">列 2</div>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="grid-content">列 3</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const message = ref('这是测试页面1')
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.test-page {
|
||||
padding: 20px;
|
||||
background: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.grid-content {
|
||||
background: #409eff;
|
||||
color: white;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
64
draggable-panels/src/views/TestPage2.vue
Normal file
64
draggable-panels/src/views/TestPage2.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div class="test-page">
|
||||
<h2>测试页面 2 - 表单布局</h2>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<div class="grid-content">表单标题区域</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20" style="margin-top: 20px;">
|
||||
<el-col :span="6">
|
||||
<div class="grid-content">标签</div>
|
||||
</el-col>
|
||||
<el-col :span="18">
|
||||
<div class="grid-content">输入框</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20" style="margin-top: 20px;">
|
||||
<el-col :span="6">
|
||||
<div class="grid-content">标签</div>
|
||||
</el-col>
|
||||
<el-col :span="18">
|
||||
<div class="grid-content">输入框</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20" style="margin-top: 20px;">
|
||||
<el-col :span="24">
|
||||
<div class="grid-content">提交按钮</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const formData = ref({
|
||||
name: '',
|
||||
email: ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.test-page {
|
||||
padding: 20px;
|
||||
background: #fafafa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.grid-content {
|
||||
background: #67c23a;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
95
draggable-panels/src/views/dashboard/Overview.vue
Normal file
95
draggable-panels/src/views/dashboard/Overview.vue
Normal file
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<div class="overview-page">
|
||||
<h2>仪表板概览</h2>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<div class="grid-content stat-card">
|
||||
<div class="stat-title">总用户</div>
|
||||
<div class="stat-value">1,234</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="grid-content stat-card">
|
||||
<div class="stat-title">活跃用户</div>
|
||||
<div class="stat-value">856</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="grid-content stat-card">
|
||||
<div class="stat-title">订单数</div>
|
||||
<div class="stat-value">432</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="grid-content stat-card">
|
||||
<div class="stat-title">收入</div>
|
||||
<div class="stat-value">¥12,345</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20" style="margin-top: 20px;">
|
||||
<el-col :span="16">
|
||||
<div class="grid-content chart-area">图表区域</div>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="grid-content">侧边栏信息</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const stats = ref({
|
||||
users: 1234,
|
||||
active: 856,
|
||||
orders: 432,
|
||||
revenue: 12345
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.overview-page {
|
||||
padding: 20px;
|
||||
background: #f0f2f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.grid-content {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-title {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
color: #333;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.chart-area {
|
||||
height: 300px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
93
draggable-panels/src/views/user/Profile.vue
Normal file
93
draggable-panels/src/views/user/Profile.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div class="profile-page">
|
||||
<h2>用户资料</h2>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<div class="grid-content avatar-section">
|
||||
<div class="avatar">头像</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="16">
|
||||
<div class="grid-content info-section">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">
|
||||
<div class="info-item">姓名: 张三</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="info-item">邮箱: zhang@example.com</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="10" style="margin-top: 10px;">
|
||||
<el-col :span="12">
|
||||
<div class="info-item">电话: 138****8888</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="info-item">部门: 技术部</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const userInfo = ref({
|
||||
name: '张三',
|
||||
email: 'zhang@example.com',
|
||||
phone: '138****8888',
|
||||
department: '技术部'
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.profile-page {
|
||||
padding: 20px;
|
||||
background: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.grid-content {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.avatar-section {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
background: #409eff;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.info-section {
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
padding: 10px;
|
||||
background: #f9f9f9;
|
||||
border-radius: 4px;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user