diff --git a/apps/cube-v1/package.json b/apps/cube-v1/package.json
new file mode 100644
index 0000000..3859970
--- /dev/null
+++ b/apps/cube-v1/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "cube-v1",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vue-tsc && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "vue": "^3.4.21"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^5.0.4",
+ "typescript": "^5.2.2",
+ "vite": "^5.2.0",
+ "vue-tsc": "^2.0.6"
+ }
+}
diff --git a/apps/cube-v1/src/main.ts b/apps/cube-v1/src/main.ts
new file mode 100644
index 0000000..6616298
--- /dev/null
+++ b/apps/cube-v1/src/main.ts
@@ -0,0 +1,14 @@
+import { createApp } from 'vue';
+import { createRouter, createWebHistory } from 'vue-router';
+import '@core/initApp';
+import App from '@core/App.vue';
+import routes from './routes/index';
+
+const router = createRouter({
+ history: createWebHistory(),
+ routes,
+});
+
+const app = createApp(App);
+app.use(router);
+app.mount('#app');
diff --git a/apps/cube-v1/src/pages/cube/apis/index.vue b/apps/cube-v1/src/pages/cube/apis/index.vue
new file mode 100644
index 0000000..39090b5
--- /dev/null
+++ b/apps/cube-v1/src/pages/cube/apis/index.vue
@@ -0,0 +1,140 @@
+<template>
+ <div class="cube-apis-container">
+ <el-card class="box-card">
+ <template #header>
+ <div class="card-header">
+ <h3>Cube API列表</h3>
+ <el-button type="primary" @click="handleGetApis">获取API列表</el-button>
+ </div>
+ </template>
+
+ <el-table :data="tableData" border style="width: 100%" v-loading="loading">
+ <el-table-column prop="id" label="ID" width="80" />
+ <el-table-column prop="name" label="API名称" min-width="150" />
+ <el-table-column prop="url" label="URL" min-width="200" />
+ <el-table-column prop="method" label="请求方法" width="100" />
+ <el-table-column prop="description" label="描述" min-width="200" />
+ <el-table-column prop="createTime" label="创建时间" width="160" />
+ </el-table>
+
+ <div class="pagination">
+ <el-pagination
+ v-model:current-page="currentPage"
+ v-model:page-size="pageSize"
+ :page-sizes="[10, 20, 50, 100]"
+ :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ @size-change="handleSizeChange"
+ @current-change="handleCurrentChange"
+ />
+ </div>
+ </el-card>
+ </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from 'vue';
+import { request } from '@core/utils/request';
+
+// 定义数据类型
+interface ApiData {
+ id: number;
+ name: string;
+ url: string;
+ method: string;
+ description: string;
+ createTime: string;
+}
+
+// 表格数据
+const tableData = ref<ApiData[]>([]);
+const loading = ref(false);
+const total = ref(0);
+const currentPage = ref(1);
+const pageSize = ref(10);
+
+// 加载数据
+const loadData = async () => {
+ loading.value = true;
+ try {
+ const response = await request.get('/Cube/Apis');
+
+ // 处理响应数据
+ if (Array.isArray(response)) {
+ tableData.value = response.map((item, index) => ({
+ id: index + 1,
+ name: item.name || `API-${index + 1}`,
+ url: item.url || item.path || '',
+ method: item.method || 'GET',
+ description: item.description || item.summary || '',
+ createTime: new Date().toLocaleString(),
+ }));
+ total.value = response.length;
+ } else if (response && typeof response === 'object') {
+ // 如果返回的是对象,尝试解析为API列表
+ const apiList: ApiData[] = [];
+ Object.keys(response).forEach((key, index) => {
+ apiList.push({
+ id: index + 1,
+ name: key,
+ url: key,
+ method: 'GET',
+ description: `${key} API`,
+ createTime: new Date().toLocaleString(),
+ });
+ });
+ tableData.value = apiList;
+ total.value = apiList.length;
+ } else {
+ tableData.value = [];
+ total.value = 0;
+ }
+ } catch {
+ tableData.value = [];
+ total.value = 0;
+ } finally {
+ loading.value = false;
+ }
+};
+
+// 获取API列表
+const handleGetApis = () => {
+ loadData();
+};
+
+// 页码变更处理
+const handleCurrentChange = (page: number) => {
+ currentPage.value = page;
+ loadData();
+};
+
+// 每页显示条数变更处理
+const handleSizeChange = (size: number) => {
+ pageSize.value = size;
+ currentPage.value = 1;
+ loadData();
+};
+
+// 初始化加载数据
+onMounted(() => {
+ loadData();
+});
+</script>
+
+<style scoped>
+.cube-apis-container {
+ padding: 20px;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.pagination {
+ margin-top: 20px;
+ display: flex;
+ justify-content: flex-end;
+}
+</style>
diff --git a/apps/cube-v1/src/pages/cube/area-all-parents/index.vue b/apps/cube-v1/src/pages/cube/area-all-parents/index.vue
new file mode 100644
index 0000000..60e8a88
--- /dev/null
+++ b/apps/cube-v1/src/pages/cube/area-all-parents/index.vue
@@ -0,0 +1,114 @@
+<template>
+ <div class="cube-area-all-parents-container">
+ <el-card class="box-card">
+ <template #header>
+ <div class="card-header">
+ <h3>获取区域所有父项</h3>
+ <el-button type="primary" @click="handleGetData">获取数据</el-button>
+ </div>
+ </template>
+
+ <el-form :inline="true" :model="searchForm" class="search-form">
+ <el-form-item label="区域ID">
+ <el-input-number v-model="searchForm.id" :min="0" placeholder="区域ID" />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleSearch">查询</el-button>
+ <el-button @click="resetSearch">重置</el-button>
+ </el-form-item>
+ </el-form>
+
+ <el-table :data="tableData" border style="width: 100%" v-loading="loading">
+ <el-table-column prop="id" label="ID" width="80" />
+ <el-table-column prop="name" label="区域名称" min-width="150" />
+ <el-table-column prop="fullName" label="全名" min-width="200" />
+ <el-table-column prop="level" label="级别" width="80" />
+ <el-table-column prop="code" label="代码" width="100" />
+ </el-table>
+ </el-card>
+ </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, onMounted } from 'vue';
+import { request } from '@core/utils/request';
+
+interface SearchForm {
+ id: number;
+}
+
+interface AreaData {
+ id: number;
+ name: string;
+ fullName: string;
+ level: number;
+ code: string;
+}
+
+const tableData = ref<AreaData[]>([]);
+const loading = ref(false);
+
+const searchForm = reactive<SearchForm>({
+ id: 0,
+});
+
+const loadData = async () => {
+ loading.value = true;
+ try {
+ const response = await request.get('/Cube/AreaAllParents', {
+ params: {
+ id: searchForm.id || undefined,
+ },
+ });
+
+ if (Array.isArray(response)) {
+ tableData.value = response.map((item, index) => ({
+ id: item.id || index + 1,
+ name: item.name || `区域${index + 1}`,
+ fullName: item.fullName || item.name || `区域${index + 1}`,
+ level: item.level || 0,
+ code: item.code || '',
+ }));
+ } else {
+ tableData.value = [];
+ }
+ } catch {
+ tableData.value = [];
+ } finally {
+ loading.value = false;
+ }
+};
+
+const handleGetData = () => {
+ loadData();
+};
+
+const handleSearch = () => {
+ loadData();
+};
+
+const resetSearch = () => {
+ searchForm.id = 0;
+ loadData();
+};
+
+onMounted(() => {
+ loadData();
+});
+</script>
+
+<style scoped>
+.cube-area-all-parents-container {
+ padding: 20px;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.search-form {
+ margin-bottom: 20px;
+}
+</style>
diff --git a/apps/cube-v1/src/pages/cube/area-childs/index.vue b/apps/cube-v1/src/pages/cube/area-childs/index.vue
new file mode 100644
index 0000000..7a1719a
--- /dev/null
+++ b/apps/cube-v1/src/pages/cube/area-childs/index.vue
@@ -0,0 +1,139 @@
+<template>
+ <div class="cube-area-childs-container">
+ <el-card class="box-card">
+ <template #header>
+ <div class="card-header">
+ <h3>获取区域子项</h3>
+ <el-button type="primary" @click="handleGetAreaChilds">获取子区域</el-button>
+ </div>
+ </template>
+
+ <el-form :inline="true" :model="searchForm" class="search-form">
+ <el-form-item label="区域ID">
+ <el-input-number v-model="searchForm.id" :min="0" placeholder="区域ID" />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleSearch">查询</el-button>
+ <el-button @click="resetSearch">重置</el-button>
+ </el-form-item>
+ </el-form>
+
+ <el-table :data="tableData" border style="width: 100%" v-loading="loading">
+ <el-table-column prop="id" label="ID" width="80" />
+ <el-table-column prop="name" label="区域名称" min-width="150" />
+ <el-table-column prop="fullName" label="全名" min-width="200" />
+ <el-table-column prop="parentId" label="上级区域ID" width="120" />
+ <el-table-column prop="level" label="级别" width="80" />
+ <el-table-column prop="code" label="代码" width="100" />
+ <el-table-column prop="pinyin" label="拼音" width="120" />
+ <el-table-column label="状态" width="80">
+ <template #default="scope">
+ <el-tag :type="scope.row.enable ? 'success' : 'danger'">
+ {{ scope.row.enable ? '启用' : '禁用' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+ </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, onMounted } from 'vue';
+import { request } from '@core/utils/request';
+
+// 定义搜索参数类型
+interface AreaParams {
+ id: number;
+}
+
+// 定义区域数据类型
+interface AreaData {
+ id: number;
+ name: string;
+ fullName: string;
+ parentId: number;
+ level: number;
+ code: string;
+ pinyin: string;
+ enable: boolean;
+}
+
+// 表格数据
+const tableData = ref<AreaData[]>([]);
+const loading = ref(false);
+
+// 查询表单
+const searchForm = reactive<AreaParams>({
+ id: 0,
+});
+
+// 加载数据
+const loadData = async () => {
+ loading.value = true;
+ try {
+ const response = await request.get('/Cube/AreaChilds', {
+ params: {
+ id: searchForm.id || undefined,
+ },
+ });
+
+ // 处理响应数据
+ if (Array.isArray(response)) {
+ tableData.value = response.map((item, index) => ({
+ id: item.id || index + 1,
+ name: item.name || `区域${index + 1}`,
+ fullName: item.fullName || item.name || `区域${index + 1}`,
+ parentId: item.parentId || item.parentID || 0,
+ level: item.level || 0,
+ code: item.code || '',
+ pinyin: item.pinyin || '',
+ enable: item.enable !== false,
+ }));
+ } else {
+ tableData.value = [];
+ }
+ } catch {
+ tableData.value = [];
+ } finally {
+ loading.value = false;
+ }
+};
+
+// 获取区域子项
+const handleGetAreaChilds = () => {
+ loadData();
+};
+
+// 搜索
+const handleSearch = () => {
+ loadData();
+};
+
+// 重置搜索
+const resetSearch = () => {
+ searchForm.id = 0;
+ loadData();
+};
+
+// 初始化加载数据
+onMounted(() => {
+ loadData();
+});
+</script>
+
+<style scoped>
+.cube-area-childs-container {
+ padding: 20px;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.search-form {
+ margin-bottom: 20px;
+}
+</style>
diff --git a/apps/cube-v1/src/pages/cube/area-parents/index.vue b/apps/cube-v1/src/pages/cube/area-parents/index.vue
new file mode 100644
index 0000000..9183e3a
--- /dev/null
+++ b/apps/cube-v1/src/pages/cube/area-parents/index.vue
@@ -0,0 +1,121 @@
+<template>
+ <div class="cube-area-parents-container">
+ <el-card class="box-card">
+ <template #header>
+ <div class="card-header">
+ <h3>获取区域父项</h3>
+ <el-button type="primary" @click="handleGetData">获取数据</el-button>
+ </div>
+ </template>
+
+ <el-form :inline="true" :model="searchForm" class="search-form">
+ <el-form-item label="区域ID">
+ <el-input-number v-model="searchForm.id" :min="0" placeholder="区域ID" />
+ </el-form-item>
+ <el-form-item label="包含自身">
+ <el-switch v-model="searchForm.isContainSelf" />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleSearch">查询</el-button>
+ <el-button @click="resetSearch">重置</el-button>
+ </el-form-item>
+ </el-form>
+
+ <el-table :data="tableData" border style="width: 100%" v-loading="loading">
+ <el-table-column prop="id" label="ID" width="80" />
+ <el-table-column prop="name" label="区域名称" min-width="150" />
+ <el-table-column prop="fullName" label="全名" min-width="200" />
+ <el-table-column prop="level" label="级别" width="80" />
+ <el-table-column prop="code" label="代码" width="100" />
+ </el-table>
+ </el-card>
+ </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, onMounted } from 'vue';
+import { request } from '@core/utils/request';
+
+interface SearchForm {
+ id: number;
+ isContainSelf: boolean;
+}
+
+interface AreaData {
+ id: number;
+ name: string;
+ fullName: string;
+ level: number;
+ code: string;
+}
+
+const tableData = ref<AreaData[]>([]);
+const loading = ref(false);
+
+const searchForm = reactive<SearchForm>({
+ id: 0,
+ isContainSelf: false,
+});
+
+const loadData = async () => {
+ loading.value = true;
+ try {
+ const response = await request.get('/Cube/AreaParents', {
+ params: {
+ id: searchForm.id || undefined,
+ isContainSelf: searchForm.isContainSelf,
+ },
+ });
+
+ if (Array.isArray(response)) {
+ tableData.value = response.map((item, index) => ({
+ id: item.id || index + 1,
+ name: item.name || `区域${index + 1}`,
+ fullName: item.fullName || item.name || `区域${index + 1}`,
+ level: item.level || 0,
+ code: item.code || '',
+ }));
+ } else {
+ tableData.value = [];
+ }
+ } catch {
+ tableData.value = [];
+ } finally {
+ loading.value = false;
+ }
+};
+
+const handleGetData = () => {
+ loadData();
+};
+
+const handleSearch = () => {
+ loadData();
+};
+
+const resetSearch = () => {
+ searchForm.id = 0;
+ searchForm.isContainSelf = false;
+ loadData();
+};
+
+onMounted(() => {
+ loadData();
+});
+</script>
+
+<style scoped>
+.cube-area-parents-container {
+ padding: 20px;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.search-form {
+ margin-bottom: 20px;
+}
+</style>
diff --git a/apps/cube-v1/src/pages/cube/avatar/index.vue b/apps/cube-v1/src/pages/cube/avatar/index.vue
new file mode 100644
index 0000000..52a0871
--- /dev/null
+++ b/apps/cube-v1/src/pages/cube/avatar/index.vue
@@ -0,0 +1,261 @@
+<template>
+ <div class="cube-avatar-container">
+ <el-card class="box-card">
+ <template #header>
+ <div class="card-header">
+ <h3>头像管理</h3>
+ <el-button type="primary" @click="handleGetAvatar">获取头像</el-button>
+ </div>
+ </template>
+
+ <el-form :inline="true" :model="searchForm" class="search-form">
+ <el-form-item label="用户ID">
+ <el-input-number v-model="searchForm.id" placeholder="请输入用户ID" :min="0" />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleGetAvatar">获取头像</el-button>
+ <el-button @click="resetSearch">重置</el-button>
+ </el-form-item>
+ </el-form>
+
+ <!-- 头像展示区域 -->
+ <el-row :gutter="20" v-if="avatarList.length > 0">
+ <el-col :span="6" v-for="avatar in avatarList" :key="avatar.id">
+ <el-card class="avatar-card">
+ <div class="avatar-container">
+ <img
+ :src="avatar.url"
+ :alt="avatar.name"
+ class="avatar-image"
+ @error="handleImageError"
+ />
+ </div>
+ <div class="avatar-info">
+ <h4>{{ avatar.name }}</h4>
+ <p>ID: {{ avatar.id }}</p>
+ <p>更新时间: {{ avatar.updateTime }}</p>
+ </div>
+ <div class="avatar-actions">
+ <el-button type="primary" size="small" @click="viewAvatar(avatar)">查看</el-button>
+ <el-button type="success" size="small" @click="downloadAvatar(avatar)">下载</el-button>
+ </div>
+ </el-card>
+ </el-col>
+ </el-row>
+
+ <!-- 如果没有头像 -->
+ <el-empty v-else-if="searchForm.id && !loading" description="未找到头像" />
+
+ <!-- 加载状态 -->
+ <div v-loading="loading" class="loading-container" />
+ </el-card>
+
+ <!-- 头像查看对话框 -->
+ <el-dialog v-model="dialogVisible" title="头像查看" width="500px">
+ <div class="dialog-avatar-container" v-if="selectedAvatar">
+ <img
+ :src="selectedAvatar.url"
+ :alt="selectedAvatar.name"
+ class="dialog-avatar-image"
+ />
+ <el-descriptions :column="1" class="avatar-details">
+ <el-descriptions-item label="用户ID">{{ selectedAvatar.id }}</el-descriptions-item>
+ <el-descriptions-item label="用户名">{{ selectedAvatar.name }}</el-descriptions-item>
+ <el-descriptions-item label="头像URL">{{ selectedAvatar.url }}</el-descriptions-item>
+ <el-descriptions-item label="更新时间">{{ selectedAvatar.updateTime }}</el-descriptions-item>
+ </el-descriptions>
+ </div>
+ <template #footer>
+ <el-button @click="dialogVisible = false">关闭</el-button>
+ <el-button type="primary" @click="downloadAvatar(selectedAvatar)">下载头像</el-button>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive } from 'vue';
+import { request } from '@core/utils/request';
+import { ElMessage } from 'element-plus';
+
+// 定义接口类型
+interface AvatarParams {
+ id?: number;
+}
+
+interface AvatarData {
+ id: number;
+ name: string;
+ url: string;
+ updateTime: string;
+}
+
+// 表单数据
+const searchForm = reactive<AvatarParams>({
+ id: undefined,
+});
+
+// 头像列表
+const avatarList = ref<AvatarData[]>([]);
+const loading = ref(false);
+const dialogVisible = ref(false);
+const selectedAvatar = ref<AvatarData | null>(null);
+
+// 获取头像
+const handleGetAvatar = async () => {
+ if (!searchForm.id) {
+ ElMessage.warning('请输入用户ID');
+ return;
+ }
+
+ loading.value = true;
+ try {
+ const response = await request.get('/Cube/Avatar', {
+ params: {
+ id: searchForm.id,
+ },
+ responseType: 'blob', // 头像通常是二进制数据
+ });
+
+ // 如果响应是blob,创建URL
+ if (response instanceof Blob) {
+ const url = URL.createObjectURL(response);
+ const avatarData: AvatarData = {
+ id: searchForm.id,
+ name: `用户${searchForm.id}`,
+ url,
+ updateTime: new Date().toLocaleString(),
+ };
+
+ avatarList.value = [avatarData];
+ ElMessage.success('头像获取成功');
+ } else {
+ // 如果返回的是URL字符串或其他格式
+ const avatarData: AvatarData = {
+ id: searchForm.id,
+ name: `用户${searchForm.id}`,
+ url: typeof response === 'string' ? response : `/Cube/Avatar?id=${searchForm.id}`,
+ updateTime: new Date().toLocaleString(),
+ };
+
+ avatarList.value = [avatarData];
+ ElMessage.success('头像获取成功');
+ }
+ } catch {
+ ElMessage.error('头像获取失败');
+ avatarList.value = [];
+ } finally {
+ loading.value = false;
+ }
+};
+
+// 重置搜索
+const resetSearch = () => {
+ searchForm.id = undefined;
+ avatarList.value = [];
+};
+
+// 查看头像
+const viewAvatar = (avatar: AvatarData) => {
+ selectedAvatar.value = avatar;
+ dialogVisible.value = true;
+};
+
+// 下载头像
+const downloadAvatar = (avatar: AvatarData | null) => {
+ if (!avatar) return;
+
+ const link = document.createElement('a');
+ link.href = avatar.url;
+ link.download = `avatar_${avatar.id}.png`;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+
+ ElMessage.success('头像下载完成');
+};
+
+// 图片加载错误处理
+const handleImageError = (event: Event) => {
+ const target = event.target as HTMLImageElement;
+ target.src = '/path/to/default-avatar.png'; // 设置默认头像
+};
+</script>
+
+<style scoped>
+.cube-avatar-container {
+ padding: 20px;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.search-form {
+ margin-bottom: 20px;
+}
+
+.avatar-card {
+ margin-bottom: 20px;
+}
+
+.avatar-container {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 150px;
+ background-color: #f5f5f5;
+ border-radius: 8px;
+ margin-bottom: 15px;
+}
+
+.avatar-image {
+ max-width: 120px;
+ max-height: 120px;
+ border-radius: 50%;
+ object-fit: cover;
+}
+
+.avatar-info {
+ text-align: center;
+ margin-bottom: 15px;
+}
+
+.avatar-info h4 {
+ margin: 0 0 8px 0;
+ color: #333;
+}
+
+.avatar-info p {
+ margin: 4px 0;
+ color: #666;
+ font-size: 12px;
+}
+
+.avatar-actions {
+ display: flex;
+ justify-content: center;
+ gap: 10px;
+}
+
+.loading-container {
+ min-height: 200px;
+}
+
+.dialog-avatar-container {
+ text-align: center;
+}
+
+.dialog-avatar-image {
+ max-width: 200px;
+ max-height: 200px;
+ border-radius: 8px;
+ margin-bottom: 20px;
+}
+
+.avatar-details {
+ margin-top: 20px;
+}
+</style>
diff --git a/apps/cube-v1/src/pages/cube/department-search/index.vue b/apps/cube-v1/src/pages/cube/department-search/index.vue
new file mode 100644
index 0000000..8f2de4f
--- /dev/null
+++ b/apps/cube-v1/src/pages/cube/department-search/index.vue
@@ -0,0 +1,186 @@
+<template>
+ <div class="cube-department-search-container">
+ <el-card class="box-card">
+ <template #header>
+ <div class="card-header">
+ <h3>部门搜索</h3>
+ <el-button type="primary" @click="handleSearch">搜索部门</el-button>
+ </div>
+ </template>
+
+ <el-form :inline="true" :model="searchForm" class="search-form">
+ <el-form-item label="上级部门ID">
+ <el-input-number v-model="searchForm.parentid" :min="-1" placeholder="上级部门ID" />
+ </el-form-item>
+ <el-form-item label="关键字">
+ <el-input v-model="searchForm.key" placeholder="请输入关键字" clearable />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleSearch">查询</el-button>
+ <el-button @click="resetSearch">重置</el-button>
+ </el-form-item>
+ </el-form>
+
+ <el-table :data="tableData" border style="width: 100%" v-loading="loading">
+ <el-table-column prop="id" label="ID" width="80" />
+ <el-table-column prop="name" label="部门名称" min-width="150" />
+ <el-table-column prop="fullName" label="全名" min-width="200" />
+ <el-table-column prop="parentId" label="上级部门ID" width="120" />
+ <el-table-column prop="sort" label="排序" width="80" />
+ <el-table-column prop="manager" label="负责人" width="100" />
+ <el-table-column prop="phone" label="电话" width="120" />
+ <el-table-column prop="address" label="地址" min-width="150" />
+ <el-table-column prop="createTime" label="创建时间" width="160" />
+ <el-table-column label="状态" width="80">
+ <template #default="scope">
+ <el-tag :type="scope.row.enable ? 'success' : 'danger'">
+ {{ scope.row.enable ? '启用' : '禁用' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <div class="pagination">
+ <el-pagination
+ v-model:current-page="currentPage"
+ v-model:page-size="pageSize"
+ :page-sizes="[10, 20, 50, 100]"
+ :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ @size-change="handleSizeChange"
+ @current-change="handleCurrentChange"
+ />
+ </div>
+ </el-card>
+ </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, onMounted } from 'vue';
+import { request } from '@core/utils/request';
+
+// 定义搜索参数类型
+interface DepartmentSearchParams {
+ parentid: number;
+ key: string;
+}
+
+// 定义部门数据类型
+interface DepartmentData {
+ id: number;
+ name: string;
+ fullName: string;
+ parentId: number;
+ sort: number;
+ manager: string;
+ phone: string;
+ address: string;
+ createTime: string;
+ enable: boolean;
+}
+
+// 表格数据
+const tableData = ref<DepartmentData[]>([]);
+const loading = ref(false);
+const total = ref(0);
+const currentPage = ref(1);
+const pageSize = ref(10);
+
+// 查询表单
+const searchForm = reactive<DepartmentSearchParams>({
+ parentid: -1,
+ key: '',
+});
+
+// 加载数据
+const loadData = async () => {
+ loading.value = true;
+ try {
+ const response = await request.get('/Cube/DepartmentSearch', {
+ params: {
+ parentid: searchForm.parentid,
+ key: searchForm.key || undefined,
+ },
+ });
+
+ // 处理响应数据
+ if (Array.isArray(response)) {
+ tableData.value = response.map((item, index) => ({
+ id: item.id || index + 1,
+ name: item.name || `部门${index + 1}`,
+ fullName: item.fullName || item.name || `部门${index + 1}`,
+ parentId: item.parentId || item.parentID || 0,
+ sort: item.sort || 0,
+ manager: item.manager || '',
+ phone: item.phone || '',
+ address: item.address || '',
+ createTime: item.createTime || new Date().toLocaleString(),
+ enable: item.enable !== false,
+ }));
+ total.value = response.length;
+ } else {
+ tableData.value = [];
+ total.value = 0;
+ }
+ } catch {
+ tableData.value = [];
+ total.value = 0;
+ } finally {
+ loading.value = false;
+ }
+};
+
+// 页码变更处理
+const handleCurrentChange = (page: number) => {
+ currentPage.value = page;
+ loadData();
+};
+
+// 每页显示条数变更处理
+const handleSizeChange = (size: number) => {
+ pageSize.value = size;
+ currentPage.value = 1;
+ loadData();
+};
+
+// 搜索
+const handleSearch = () => {
+ currentPage.value = 1;
+ loadData();
+};
+
+// 重置搜索
+const resetSearch = () => {
+ searchForm.parentid = -1;
+ searchForm.key = '';
+ currentPage.value = 1;
+ loadData();
+};
+
+// 初始化加载数据
+onMounted(() => {
+ loadData();
+});
+</script>
+
+<style scoped>
+.cube-department-search-container {
+ padding: 20px;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.search-form {
+ margin-bottom: 20px;
+}
+
+.pagination {
+ margin-top: 20px;
+ display: flex;
+ justify-content: flex-end;
+}
+</style>
diff --git a/apps/cube-v1/src/pages/cube/file/index.vue b/apps/cube-v1/src/pages/cube/file/index.vue
new file mode 100644
index 0000000..8d89094
--- /dev/null
+++ b/apps/cube-v1/src/pages/cube/file/index.vue
@@ -0,0 +1,302 @@
+<template>
+ <div class="cube-file-container">
+ <el-card class="box-card">
+ <template #header>
+ <div class="card-header">
+ <h3>文件管理</h3>
+ <el-button type="primary" @click="handleGetFile">获取文件</el-button>
+ </div>
+ </template>
+
+ <el-form :inline="true" :model="searchForm" class="search-form">
+ <el-form-item label="文件ID">
+ <el-input v-model="searchForm.id" placeholder="请输入文件ID" clearable />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleGetFile">获取文件</el-button>
+ <el-button @click="resetSearch">重置</el-button>
+ </el-form-item>
+ </el-form>
+
+ <!-- 文件列表 -->
+ <el-table :data="fileList" border style="width: 100%" v-loading="loading">
+ <el-table-column prop="id" label="文件ID" width="120" />
+ <el-table-column prop="name" label="文件名" min-width="200" />
+ <el-table-column prop="size" label="文件大小" width="100">
+ <template #default="scope">
+ {{ formatFileSize(scope.row.size) }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="type" label="文件类型" width="120" />
+ <el-table-column prop="url" label="文件URL" min-width="200" show-overflow-tooltip />
+ <el-table-column prop="uploadTime" label="上传时间" width="160" />
+ <el-table-column label="操作" width="200">
+ <template #default="scope">
+ <el-button type="primary" size="small" @click="previewFile(scope.row)">预览</el-button>
+ <el-button type="success" size="small" @click="downloadFile(scope.row)">下载</el-button>
+ <el-button type="info" size="small" @click="copyFileUrl(scope.row)">复制链接</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <div class="pagination">
+ <el-pagination
+ v-model:current-page="currentPage"
+ v-model:page-size="pageSize"
+ :page-sizes="[10, 20, 50, 100]"
+ :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ @size-change="handleSizeChange"
+ @current-change="handleCurrentChange"
+ />
+ </div>
+ </el-card>
+
+ <!-- 文件预览对话框 -->
+ <el-dialog v-model="previewVisible" :title="previewFile.name" width="80%">
+ <div class="file-preview-container">
+ <!-- 图片预览 -->
+ <div v-if="isImageFile(selectedFile)" class="image-preview">
+ <img :src="selectedFile.url" :alt="selectedFile.name" class="preview-image" />
+ </div>
+ <!-- 文本文件预览 -->
+ <div v-else-if="isTextFile(selectedFile)" class="text-preview">
+ <el-input
+ v-model="fileContent"
+ type="textarea"
+ :rows="20"
+ readonly
+ placeholder="文本内容将显示在这里"
+ />
+ </div>
+ <!-- 其他文件类型 -->
+ <div v-else class="other-file-preview">
+ <el-empty description="该文件类型不支持预览">
+ <el-button type="primary" @click="downloadFile(selectedFile)">下载文件</el-button>
+ </el-empty>
+ </div>
+ </div>
+ <template #footer>
+ <el-button @click="previewVisible = false">关闭</el-button>
+ <el-button type="primary" @click="downloadFile(selectedFile)">下载</el-button>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive } from 'vue';
+import { request } from '@core/utils/request';
+import { ElMessage } from 'element-plus';
+
+// 定义接口类型
+interface FileParams {
+ id?: string;
+}
+
+interface FileData {
+ id: string;
+ name: string;
+ size: number;
+ type: string;
+ url: string;
+ uploadTime: string;
+}
+
+// 表单数据
+const searchForm = reactive<FileParams>({
+ id: '',
+});
+
+// 文件列表
+const fileList = ref<FileData[]>([]);
+const loading = ref(false);
+const total = ref(0);
+const currentPage = ref(1);
+const pageSize = ref(10);
+
+// 预览相关
+const previewVisible = ref(false);
+const selectedFile = ref<FileData | null>(null);
+const fileContent = ref('');
+
+// 获取文件
+const handleGetFile = async () => {
+ if (!searchForm.id) {
+ ElMessage.warning('请输入文件ID');
+ return;
+ }
+
+ loading.value = true;
+ try {
+ const response = await request.get('/Cube/File', {
+ params: {
+ id: searchForm.id,
+ },
+ });
+
+ // 处理响应数据
+ if (response) {
+ const fileData: FileData = {
+ id: searchForm.id,
+ name: response.name || response.fileName || `文件_${searchForm.id}`,
+ size: response.size || 0,
+ type: response.type || response.contentType || 'unknown',
+ url: response.url || `/Cube/File?id=${searchForm.id}`,
+ uploadTime: response.uploadTime || new Date().toLocaleString(),
+ };
+
+ fileList.value = [fileData];
+ total.value = 1;
+ ElMessage.success('文件获取成功');
+ } else {
+ fileList.value = [];
+ total.value = 0;
+ }
+ } catch {
+ ElMessage.error('文件获取失败');
+ fileList.value = [];
+ total.value = 0;
+ } finally {
+ loading.value = false;
+ }
+};
+
+// 重置搜索
+const resetSearch = () => {
+ searchForm.id = '';
+ fileList.value = [];
+ total.value = 0;
+};
+
+// 预览文件
+const previewFile = (file: FileData) => {
+ selectedFile.value = file;
+ previewVisible.value = true;
+
+ // 如果是文本文件,尝试获取内容
+ if (isTextFile(file)) {
+ loadTextContent(file);
+ }
+};
+
+// 下载文件
+const downloadFile = (file: FileData) => {
+ const link = document.createElement('a');
+ link.href = file.url;
+ link.download = file.name;
+ link.target = '_blank';
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+
+ ElMessage.success('文件下载完成');
+};
+
+// 复制文件链接
+const copyFileUrl = async (file: FileData) => {
+ try {
+ await navigator.clipboard.writeText(file.url);
+ ElMessage.success('文件链接已复制到剪贴板');
+ } catch {
+ ElMessage.error('复制失败,请手动复制');
+ }
+};
+
+// 判断是否为图片文件
+const isImageFile = (file: FileData | null): boolean => {
+ if (!file) return false;
+ const imageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/webp'];
+ return imageTypes.includes(file.type.toLowerCase()) ||
+ /\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(file.name);
+};
+
+// 判断是否为文本文件
+const isTextFile = (file: FileData | null): boolean => {
+ if (!file) return false;
+ const textTypes = ['text/plain', 'text/html', 'text/css', 'text/javascript', 'application/json'];
+ return textTypes.includes(file.type.toLowerCase()) ||
+ /\.(txt|html|css|js|json|xml|md)$/i.test(file.name);
+};
+
+// 加载文本内容
+const loadTextContent = async (file: FileData) => {
+ try {
+ const response = await request.get(file.url, {
+ responseType: 'text',
+ });
+ fileContent.value = response;
+ } catch {
+ fileContent.value = '文本内容加载失败';
+ }
+};
+
+// 格式化文件大小
+const formatFileSize = (size: number): string => {
+ if (size === 0) return '0 B';
+ const k = 1024;
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
+ const i = Math.floor(Math.log(size) / Math.log(k));
+ return parseFloat((size / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+};
+
+// 页码变更处理
+const handleCurrentChange = (page: number) => {
+ currentPage.value = page;
+};
+
+// 每页显示条数变更处理
+const handleSizeChange = (size: number) => {
+ pageSize.value = size;
+ currentPage.value = 1;
+};
+</script>
+
+<style scoped>
+.cube-file-container {
+ padding: 20px;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.search-form {
+ margin-bottom: 20px;
+}
+
+.pagination {
+ margin-top: 20px;
+ display: flex;
+ justify-content: flex-end;
+}
+
+.file-preview-container {
+ min-height: 400px;
+}
+
+.image-preview {
+ text-align: center;
+ padding: 20px;
+}
+
+.preview-image {
+ max-width: 100%;
+ max-height: 500px;
+ border-radius: 8px;
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+}
+
+.text-preview {
+ padding: 20px;
+}
+
+.other-file-preview {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 300px;
+}
+</style>
diff --git a/apps/cube-v1/src/pages/cube/get-area/index.vue b/apps/cube-v1/src/pages/cube/get-area/index.vue
new file mode 100644
index 0000000..4862a2d
--- /dev/null
+++ b/apps/cube-v1/src/pages/cube/get-area/index.vue
@@ -0,0 +1,199 @@
+<template>
+ <div class="cube-get-area-container">
+ <el-card class="box-card">
+ <template #header>
+ <div class="card-header">
+ <h3>获取区域</h3>
+ <el-button type="primary" @click="handleGetArea">获取区域信息</el-button>
+ </div>
+ </template>
+
+ <el-form :inline="true" :model="searchForm" class="search-form">
+ <el-form-item label="区域ID">
+ <el-input-number v-model="searchForm.id" :min="0" placeholder="区域ID" />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleSearch">查询</el-button>
+ <el-button @click="resetSearch">重置</el-button>
+ </el-form-item>
+ </el-form>
+
+ <el-table :data="tableData" border style="width: 100%" v-loading="loading">
+ <el-table-column prop="id" label="ID" width="80" />
+ <el-table-column prop="name" label="区域名称" min-width="150" />
+ <el-table-column prop="fullName" label="全名" min-width="200" />
+ <el-table-column prop="parentId" label="上级区域ID" width="120" />
+ <el-table-column prop="level" label="级别" width="80" />
+ <el-table-column prop="code" label="代码" width="100" />
+ <el-table-column prop="pinyin" label="拼音" width="120" />
+ <el-table-column prop="latitude" label="纬度" width="100" />
+ <el-table-column prop="longitude" label="经度" width="100" />
+ <el-table-column label="状态" width="80">
+ <template #default="scope">
+ <el-tag :type="scope.row.enable ? 'success' : 'danger'">
+ {{ scope.row.enable ? '启用' : '禁用' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <div class="pagination">
+ <el-pagination
+ v-model:current-page="currentPage"
+ v-model:page-size="pageSize"
+ :page-sizes="[10, 20, 50, 100]"
+ :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ @size-change="handleSizeChange"
+ @current-change="handleCurrentChange"
+ />
+ </div>
+ </el-card>
+ </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, onMounted } from 'vue';
+import { request } from '@core/utils/request';
+
+// 定义搜索参数类型
+interface AreaParams {
+ id: number;
+}
+
+// 定义区域数据类型
+interface AreaData {
+ id: number;
+ name: string;
+ fullName: string;
+ parentId: number;
+ level: number;
+ code: string;
+ pinyin: string;
+ latitude: number;
+ longitude: number;
+ enable: boolean;
+}
+
+// 表格数据
+const tableData = ref<AreaData[]>([]);
+const loading = ref(false);
+const total = ref(0);
+const currentPage = ref(1);
+const pageSize = ref(10);
+
+// 查询表单
+const searchForm = reactive<AreaParams>({
+ id: 0,
+});
+
+// 加载数据
+const loadData = async () => {
+ loading.value = true;
+ try {
+ const response = await request.get('/Cube/GetArea', {
+ params: {
+ id: searchForm.id || undefined,
+ },
+ });
+
+ // 处理响应数据
+ if (Array.isArray(response)) {
+ tableData.value = response.map((item, index) => ({
+ id: item.id || index + 1,
+ name: item.name || `区域${index + 1}`,
+ fullName: item.fullName || item.name || `区域${index + 1}`,
+ parentId: item.parentId || item.parentID || 0,
+ level: item.level || 0,
+ code: item.code || '',
+ pinyin: item.pinyin || '',
+ latitude: item.latitude || 0,
+ longitude: item.longitude || 0,
+ enable: item.enable !== false,
+ }));
+ total.value = response.length;
+ } else if (response && typeof response === 'object') {
+ // 如果返回单个对象,转换为数组
+ tableData.value = [{
+ id: response.id || 1,
+ name: response.name || '区域',
+ fullName: response.fullName || response.name || '区域',
+ parentId: response.parentId || response.parentID || 0,
+ level: response.level || 0,
+ code: response.code || '',
+ pinyin: response.pinyin || '',
+ latitude: response.latitude || 0,
+ longitude: response.longitude || 0,
+ enable: response.enable !== false,
+ }];
+ total.value = 1;
+ } else {
+ tableData.value = [];
+ total.value = 0;
+ }
+ } catch {
+ tableData.value = [];
+ total.value = 0;
+ } finally {
+ loading.value = false;
+ }
+};
+
+// 获取区域
+const handleGetArea = () => {
+ loadData();
+};
+
+// 页码变更处理
+const handleCurrentChange = (page: number) => {
+ currentPage.value = page;
+ loadData();
+};
+
+// 每页显示条数变更处理
+const handleSizeChange = (size: number) => {
+ pageSize.value = size;
+ currentPage.value = 1;
+ loadData();
+};
+
+// 搜索
+const handleSearch = () => {
+ currentPage.value = 1;
+ loadData();
+};
+
+// 重置搜索
+const resetSearch = () => {
+ searchForm.id = 0;
+ currentPage.value = 1;
+ loadData();
+};
+
+// 初始化加载数据
+onMounted(() => {
+ loadData();
+});
+</script>
+
+<style scoped>
+.cube-get-area-container {
+ padding: 20px;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.search-form {
+ margin-bottom: 20px;
+}
+
+.pagination {
+ margin-top: 20px;
+ display: flex;
+ justify-content: flex-end;
+}
+</style>
diff --git a/apps/cube-v1/src/pages/cube/get-page-config/index.vue b/apps/cube-v1/src/pages/cube/get-page-config/index.vue
new file mode 100644
index 0000000..7ae0536
--- /dev/null
+++ b/apps/cube-v1/src/pages/cube/get-page-config/index.vue
@@ -0,0 +1,231 @@
+<template>
+ <div class="get-page-config-container">
+ <el-card class="box-card">
+ <template #header>
+ <div class="card-header">
+ <h3>获取页面配置</h3>
+ <el-button type="primary" @click="handleGetConfig">获取配置</el-button>
+ </div>
+ </template>
+
+ <el-form :inline="true" :model="searchForm" class="search-form">
+ <el-form-item label="类型">
+ <el-input v-model="searchForm.kind" placeholder="请输入配置类型" clearable />
+ </el-form-item>
+ <el-form-item label="页面">
+ <el-input v-model="searchForm.page" placeholder="请输入页面名称" clearable />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleGetConfig">获取配置</el-button>
+ <el-button @click="resetSearch">重置</el-button>
+ </el-form-item>
+ </el-form>
+
+ <!-- 配置结果展示 -->
+ <el-card v-if="configData" class="config-card">
+ <template #header>
+ <h4>配置结果</h4>
+ </template>
+ <el-descriptions :column="1" border>
+ <el-descriptions-item label="类型">
+ {{ searchForm.kind }}
+ </el-descriptions-item>
+ <el-descriptions-item label="页面">
+ {{ searchForm.page }}
+ </el-descriptions-item>
+ <el-descriptions-item label="配置数据">
+ <el-input
+ v-model="formattedConfig"
+ type="textarea"
+ :rows="15"
+ readonly
+ placeholder="配置数据将显示在这里"
+ />
+ </el-descriptions-item>
+ </el-descriptions>
+ <div class="config-actions">
+ <el-button type="primary" @click="copyConfig">复制配置</el-button>
+ <el-button type="success" @click="downloadConfig">下载配置</el-button>
+ </div>
+ </el-card>
+
+ <!-- 历史查询记录 -->
+ <el-card v-if="queryHistory.length > 0" class="history-card">
+ <template #header>
+ <h4>查询历史</h4>
+ </template>
+ <el-table :data="queryHistory" border style="width: 100%">
+ <el-table-column prop="kind" label="类型" width="150" />
+ <el-table-column prop="page" label="页面" width="150" />
+ <el-table-column prop="queryTime" label="查询时间" width="160" />
+ <el-table-column prop="hasData" label="是否有数据" width="100">
+ <template #default="scope">
+ <el-tag :type="scope.row.hasData ? 'success' : 'warning'">
+ {{ scope.row.hasData ? '有数据' : '无数据' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="操作" width="120">
+ <template #default="scope">
+ <el-button type="primary" size="small" @click="loadHistory(scope.row)">重新查询</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+ </el-card>
+ </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, computed } from 'vue';
+import { request } from '@core/utils/request';
+import { ElMessage } from 'element-plus';
+
+// 定义接口类型
+interface PageConfigParams {
+ kind?: string;
+ page?: string;
+}
+
+interface QueryHistoryItem extends PageConfigParams {
+ queryTime: string;
+ hasData: boolean;
+}
+
+// 查询表单
+const searchForm = reactive<PageConfigParams>({
+ kind: '',
+ page: '',
+});
+
+// 配置数据
+const configData = ref<unknown>(null);
+const queryHistory = ref<QueryHistoryItem[]>([]);
+
+// 格式化后的配置数据
+const formattedConfig = computed(() => {
+ if (!configData.value) return '';
+ try {
+ return JSON.stringify(configData.value, null, 2);
+ } catch {
+ return String(configData.value);
+ }
+});
+
+// 获取配置
+const handleGetConfig = async () => {
+ if (!searchForm.kind || !searchForm.page) {
+ ElMessage.warning('请输入类型和页面名称');
+ return;
+ }
+
+ try {
+ const response = await request.get('/Cube/GetPageConfig', {
+ params: {
+ kind: searchForm.kind,
+ page: searchForm.page,
+ },
+ });
+
+ configData.value = response;
+
+ // 添加到查询历史
+ queryHistory.value.unshift({
+ kind: searchForm.kind,
+ page: searchForm.page,
+ queryTime: new Date().toLocaleString(),
+ hasData: response !== null && response !== undefined,
+ });
+
+ // 保持历史记录在10条以内
+ if (queryHistory.value.length > 10) {
+ queryHistory.value = queryHistory.value.slice(0, 10);
+ }
+
+ ElMessage.success('配置获取成功');
+ } catch {
+ ElMessage.error('配置获取失败');
+ configData.value = null;
+ }
+};
+
+// 重置搜索
+const resetSearch = () => {
+ searchForm.kind = '';
+ searchForm.page = '';
+ configData.value = null;
+};
+
+// 复制配置
+const copyConfig = async () => {
+ if (!formattedConfig.value) {
+ ElMessage.warning('没有配置数据可复制');
+ return;
+ }
+
+ try {
+ await navigator.clipboard.writeText(formattedConfig.value);
+ ElMessage.success('配置已复制到剪贴板');
+ } catch {
+ ElMessage.error('复制失败,请手动复制');
+ }
+};
+
+// 下载配置
+const downloadConfig = () => {
+ if (!formattedConfig.value) {
+ ElMessage.warning('没有配置数据可下载');
+ return;
+ }
+
+ const blob = new Blob([formattedConfig.value], { type: 'application/json' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `${searchForm.kind}_${searchForm.page}_config.json`;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
+
+ ElMessage.success('配置文件下载完成');
+};
+
+// 载入历史记录
+const loadHistory = (item: QueryHistoryItem) => {
+ Object.assign(searchForm, {
+ kind: item.kind,
+ page: item.page,
+ });
+ handleGetConfig();
+};
+</script>
+
+<style scoped>
+.get-page-config-container {
+ padding: 20px;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.search-form {
+ margin-bottom: 20px;
+}
+
+.config-card {
+ margin-top: 20px;
+}
+
+.config-actions {
+ margin-top: 15px;
+ text-align: right;
+}
+
+.history-card {
+ margin-top: 20px;
+}
+</style>
diff --git a/apps/cube-v1/src/pages/cube/info/index.vue b/apps/cube-v1/src/pages/cube/info/index.vue
new file mode 100644
index 0000000..bbebc89
--- /dev/null
+++ b/apps/cube-v1/src/pages/cube/info/index.vue
@@ -0,0 +1,162 @@
+<template>
+ <div class="cube-info-container">
+ <el-card class="box-card">
+ <template #header>
+ <div class="card-header">
+ <h3>Cube信息</h3>
+ <el-button type="primary" @click="handleGetInfo">获取信息</el-button>
+ </div>
+ </template>
+
+ <el-form :inline="true" :model="searchForm" class="search-form">
+ <el-form-item label="状态">
+ <el-input v-model="searchForm.state" placeholder="请输入状态" clearable />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleSearch">查询</el-button>
+ <el-button @click="resetSearch">重置</el-button>
+ </el-form-item>
+ </el-form>
+
+ <el-table :data="tableData" border style="width: 100%" v-loading="loading">
+ <el-table-column prop="key" label="键" />
+ <el-table-column prop="value" label="值" />
+ <el-table-column prop="description" label="描述" />
+ </el-table>
+
+ <div class="pagination">
+ <el-pagination
+ v-model:current-page="currentPage"
+ v-model:page-size="pageSize"
+ :page-sizes="[10, 20, 50, 100]"
+ :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ @size-change="handleSizeChange"
+ @current-change="handleCurrentChange"
+ />
+ </div>
+ </el-card>
+ </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, onMounted } from 'vue';
+import { request } from '@core/utils/request';
+
+// 定义接口类型
+interface CubeInfo {
+ state?: string;
+}
+
+// 定义数据类型
+interface CubeInfoData {
+ key: string;
+ value: string;
+ description: string;
+}
+
+// 表格数据
+const tableData = ref<CubeInfoData[]>([]);
+const loading = ref(false);
+const total = ref(0);
+const currentPage = ref(1);
+const pageSize = ref(10);
+
+// 查询表单
+const searchForm = reactive<CubeInfo>({
+ state: '',
+});
+
+// 加载数据
+const loadData = async () => {
+ loading.value = true;
+ try {
+ const response = await request.get('/Cube/Info', {
+ params: {
+ state: searchForm.state,
+ },
+ });
+
+ // 处理响应数据
+ if (response && typeof response === 'object') {
+ const dataArray: CubeInfoData[] = [];
+ Object.keys(response).forEach((key) => {
+ const value = response[key as keyof typeof response];
+ dataArray.push({
+ key,
+ value: String(value),
+ description: `${key}的值`,
+ });
+ });
+ tableData.value = dataArray;
+ total.value = dataArray.length;
+ } else {
+ tableData.value = [];
+ total.value = 0;
+ }
+ } catch {
+ tableData.value = [];
+ total.value = 0;
+ } finally {
+ loading.value = false;
+ }
+};
+
+// 获取信息
+const handleGetInfo = () => {
+ loadData();
+};
+
+// 页码变更处理
+const handleCurrentChange = (page: number) => {
+ currentPage.value = page;
+ loadData();
+};
+
+// 每页显示条数变更处理
+const handleSizeChange = (size: number) => {
+ pageSize.value = size;
+ currentPage.value = 1;
+ loadData();
+};
+
+// 搜索
+const handleSearch = () => {
+ currentPage.value = 1;
+ loadData();
+};
+
+// 重置搜索
+const resetSearch = () => {
+ searchForm.state = '';
+ currentPage.value = 1;
+ loadData();
+};
+
+// 初始化加载数据
+onMounted(() => {
+ loadData();
+});
+</script>
+
+<style scoped>
+.cube-info-container {
+ padding: 20px;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.search-form {
+ margin-bottom: 20px;
+}
+
+.pagination {
+ margin-top: 20px;
+ display: flex;
+ justify-content: flex-end;
+}
+</style>
diff --git a/apps/cube-v1/src/pages/cube/lookup/index.vue b/apps/cube-v1/src/pages/cube/lookup/index.vue
new file mode 100644
index 0000000..d797ca9
--- /dev/null
+++ b/apps/cube-v1/src/pages/cube/lookup/index.vue
@@ -0,0 +1,186 @@
+<template>
+ <div class="cube-lookup-container">
+ <el-card class="box-card">
+ <template #header>
+ <div class="card-header">
+ <h3>Cube Lookup 查询</h3>
+ <el-button type="primary" @click="handleLookup">查询</el-button>
+ </div>
+ </template>
+
+ <el-form :inline="true" :model="searchForm" class="search-form">
+ <el-form-item label="代码">
+ <el-input v-model="searchForm.codes" placeholder="请输入代码,多个用逗号分隔" clearable />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleLookup">查询</el-button>
+ <el-button @click="resetSearch">重置</el-button>
+ </el-form-item>
+ </el-form>
+
+ <el-table :data="tableData" border style="width: 100%" v-loading="loading">
+ <el-table-column prop="code" label="代码" width="120" />
+ <el-table-column prop="name" label="名称" min-width="150" />
+ <el-table-column prop="value" label="值" min-width="200" />
+ <el-table-column prop="description" label="描述" min-width="200" />
+ <el-table-column prop="category" label="分类" width="120" />
+ <el-table-column prop="sort" label="排序" width="80" />
+ <el-table-column prop="status" label="状态" width="80">
+ <template #default="scope">
+ <el-tag :type="scope.row.status ? 'success' : 'danger'">
+ {{ scope.row.status ? '启用' : '禁用' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <div class="pagination">
+ <el-pagination
+ v-model:current-page="currentPage"
+ v-model:page-size="pageSize"
+ :page-sizes="[10, 20, 50, 100]"
+ :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ @size-change="handleSizeChange"
+ @current-change="handleCurrentChange"
+ />
+ </div>
+ </el-card>
+ </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, onMounted } from 'vue';
+import { request } from '@core/utils/request';
+
+// 定义接口类型
+interface LookupParams {
+ codes: string;
+}
+
+interface LookupData {
+ code: string;
+ name: string;
+ value: string;
+ description: string;
+ category: string;
+ sort: number;
+ status: boolean;
+}
+
+// 表格数据
+const tableData = ref<LookupData[]>([]);
+const loading = ref(false);
+const total = ref(0);
+const currentPage = ref(1);
+const pageSize = ref(10);
+
+// 查询表单
+const searchForm = reactive<LookupParams>({
+ codes: '',
+});
+
+// 加载数据
+const loadData = async () => {
+ loading.value = true;
+ try {
+ const response = await request.get('/Cube/Lookup', {
+ params: {
+ codes: searchForm.codes || undefined,
+ },
+ });
+
+ // 处理响应数据
+ if (Array.isArray(response)) {
+ tableData.value = response.map((item, index) => ({
+ code: item.code || item.key || `CODE_${index + 1}`,
+ name: item.name || item.displayName || '',
+ value: item.value || item.val || '',
+ description: item.description || item.desc || item.remark || '',
+ category: item.category || item.type || '',
+ sort: item.sort || item.order || 0,
+ status: item.status !== false,
+ }));
+ total.value = response.length;
+ } else if (response && typeof response === 'object') {
+ // 如果返回的是对象,尝试转换为数组
+ const lookupList: LookupData[] = [];
+ Object.keys(response).forEach((key, index) => {
+ const item = response[key as keyof typeof response];
+ lookupList.push({
+ code: key,
+ name: typeof item === 'object' && item !== null ? (item as Record<string, unknown>).name as string || key : key,
+ value: String(item),
+ description: typeof item === 'object' && item !== null ? (item as Record<string, unknown>).description as string || '' : '',
+ category: typeof item === 'object' && item !== null ? (item as Record<string, unknown>).category as string || '' : '',
+ sort: index,
+ status: true,
+ });
+ });
+ tableData.value = lookupList;
+ total.value = lookupList.length;
+ } else {
+ tableData.value = [];
+ total.value = 0;
+ }
+ } catch {
+ tableData.value = [];
+ total.value = 0;
+ } finally {
+ loading.value = false;
+ }
+};
+
+// 页码变更处理
+const handleCurrentChange = (page: number) => {
+ currentPage.value = page;
+ loadData();
+};
+
+// 每页显示条数变更处理
+const handleSizeChange = (size: number) => {
+ pageSize.value = size;
+ currentPage.value = 1;
+ loadData();
+};
+
+// 查询
+const handleLookup = () => {
+ currentPage.value = 1;
+ loadData();
+};
+
+// 重置搜索
+const resetSearch = () => {
+ searchForm.codes = '';
+ currentPage.value = 1;
+ loadData();
+};
+
+// 初始化加载数据
+onMounted(() => {
+ loadData();
+});
+</script>
+
+<style scoped>
+.cube-lookup-container {
+ padding: 20px;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.search-form {
+ margin-bottom: 20px;
+}
+
+.pagination {
+ margin-top: 20px;
+ display: flex;
+ justify-content: flex-end;
+}
+</style>
diff --git a/apps/cube-v1/src/pages/cube/save-layout/index.vue b/apps/cube-v1/src/pages/cube/save-layout/index.vue
new file mode 100644
index 0000000..e175a01
--- /dev/null
+++ b/apps/cube-v1/src/pages/cube/save-layout/index.vue
@@ -0,0 +1,202 @@
+<template>
+ <div class="save-layout-container">
+ <el-card class="box-card">
+ <template #header>
+ <div class="card-header">
+ <h3>保存布局配置</h3>
+ <el-button type="primary" @click="handleSaveLayout">保存布局</el-button>
+ </div>
+ </template>
+
+ <el-form :model="layoutForm" :rules="formRules" ref="formRef" label-width="120px" class="layout-form">
+ <el-form-item label="用户ID" prop="userid">
+ <el-input-number v-model="layoutForm.userid" placeholder="请输入用户ID" :min="0" />
+ </el-form-item>
+ <el-form-item label="分类" prop="category">
+ <el-input v-model="layoutForm.category" placeholder="请输入分类" />
+ </el-form-item>
+ <el-form-item label="名称" prop="name">
+ <el-input v-model="layoutForm.name" placeholder="请输入名称" />
+ </el-form-item>
+ <el-form-item label="配置值" prop="value">
+ <el-input
+ v-model="layoutForm.value"
+ type="textarea"
+ :rows="10"
+ placeholder="请输入配置值(JSON格式)"
+ />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleSaveLayout" :loading="saving">保存布局</el-button>
+ <el-button @click="resetForm">重置</el-button>
+ <el-button @click="formatJson">格式化JSON</el-button>
+ </el-form-item>
+ </el-form>
+
+ <!-- 保存历史记录 -->
+ <el-card v-if="saveHistory.length > 0" class="history-card">
+ <template #header>
+ <h4>保存历史</h4>
+ </template>
+ <el-table :data="saveHistory" border style="width: 100%">
+ <el-table-column prop="userid" label="用户ID" width="80" />
+ <el-table-column prop="category" label="分类" width="120" />
+ <el-table-column prop="name" label="名称" min-width="150" />
+ <el-table-column prop="value" label="配置值" min-width="200" show-overflow-tooltip />
+ <el-table-column prop="saveTime" label="保存时间" width="160" />
+ <el-table-column label="操作" width="120">
+ <template #default="scope">
+ <el-button type="primary" size="small" @click="loadHistory(scope.row)">载入</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+ </el-card>
+ </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive } from 'vue';
+import { request } from '@core/utils/request';
+import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
+
+// 定义接口类型
+interface SaveLayoutParams {
+ userid?: number;
+ category?: string;
+ name?: string;
+ value?: string;
+}
+
+interface HistoryItem extends SaveLayoutParams {
+ saveTime: string;
+}
+
+// 表单引用
+const formRef = ref<FormInstance | null>(null);
+const saving = ref(false);
+
+// 表单数据
+const layoutForm = reactive<SaveLayoutParams>({
+ userid: 0,
+ category: '',
+ name: '',
+ value: '',
+});
+
+// 保存历史
+const saveHistory = ref<HistoryItem[]>([]);
+
+// 表单验证规则
+const formRules = reactive<FormRules>({
+ userid: [
+ { required: true, message: '请输入用户ID', trigger: 'blur' },
+ ],
+ category: [
+ { required: true, message: '请输入分类', trigger: 'blur' },
+ ],
+ name: [
+ { required: true, message: '请输入名称', trigger: 'blur' },
+ ],
+ value: [
+ { required: true, message: '请输入配置值', trigger: 'blur' },
+ ],
+});
+
+// 保存布局
+const handleSaveLayout = async () => {
+ if (!formRef.value) return;
+
+ formRef.value.validate(async (valid: boolean) => {
+ if (valid) {
+ saving.value = true;
+ try {
+ await request.post('/Cube/SaveLayout', null, {
+ params: {
+ userid: layoutForm.userid,
+ category: layoutForm.category,
+ name: layoutForm.name,
+ value: layoutForm.value,
+ },
+ });
+
+ ElMessage.success('布局保存成功');
+
+ // 添加到历史记录
+ saveHistory.value.unshift({
+ ...layoutForm,
+ saveTime: new Date().toLocaleString(),
+ });
+
+ // 保持历史记录在10条以内
+ if (saveHistory.value.length > 10) {
+ saveHistory.value = saveHistory.value.slice(0, 10);
+ }
+
+ } catch (error) {
+ ElMessage.error('布局保存失败');
+ console.error('Save layout error:', error);
+ } finally {
+ saving.value = false;
+ }
+ }
+ });
+};
+
+// 重置表单
+const resetForm = () => {
+ if (formRef.value) {
+ formRef.value.resetFields();
+ }
+ Object.assign(layoutForm, {
+ userid: 0,
+ category: '',
+ name: '',
+ value: '',
+ });
+};
+
+// 格式化JSON
+const formatJson = () => {
+ if (layoutForm.value) {
+ try {
+ const parsed = JSON.parse(layoutForm.value);
+ layoutForm.value = JSON.stringify(parsed, null, 2);
+ ElMessage.success('JSON格式化成功');
+ } catch {
+ ElMessage.error('JSON格式不正确');
+ }
+ }
+};
+
+// 载入历史记录
+const loadHistory = (item: HistoryItem) => {
+ Object.assign(layoutForm, {
+ userid: item.userid,
+ category: item.category,
+ name: item.name,
+ value: item.value,
+ });
+ ElMessage.success('历史记录已载入');
+};
+</script>
+
+<style scoped>
+.save-layout-container {
+ padding: 20px;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.layout-form {
+ max-width: 800px;
+}
+
+.history-card {
+ margin-top: 20px;
+}
+</style>
diff --git a/apps/cube-v1/src/pages/cube/set-page-config/index.vue b/apps/cube-v1/src/pages/cube/set-page-config/index.vue
new file mode 100644
index 0000000..63fb94c
--- /dev/null
+++ b/apps/cube-v1/src/pages/cube/set-page-config/index.vue
@@ -0,0 +1,327 @@
+<template>
+ <div class="set-page-config-container">
+ <el-card class="box-card">
+ <template #header>
+ <div class="card-header">
+ <h3>设置页面配置</h3>
+ <el-button type="primary" @click="handleSetConfig">保存配置</el-button>
+ </div>
+ </template>
+
+ <el-form :model="configForm" :rules="formRules" ref="formRef" label-width="120px" class="config-form">
+ <el-form-item label="类型" prop="kind">
+ <el-input v-model="configForm.kind" placeholder="请输入配置类型" />
+ </el-form-item>
+ <el-form-item label="页面" prop="page">
+ <el-input v-model="configForm.page" placeholder="请输入页面名称" />
+ </el-form-item>
+ <el-form-item label="配置数据" prop="configData">
+ <el-input
+ v-model="configForm.configData"
+ type="textarea"
+ :rows="15"
+ placeholder="请输入配置数据(JSON格式)"
+ />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleSetConfig" :loading="saving">保存配置</el-button>
+ <el-button @click="resetForm">重置</el-button>
+ <el-button @click="formatJson">格式化JSON</el-button>
+ <el-button @click="validateJson">验证JSON</el-button>
+ </el-form-item>
+ </el-form>
+
+ <!-- 配置模板 -->
+ <el-card class="template-card">
+ <template #header>
+ <h4>配置模板</h4>
+ </template>
+ <el-row :gutter="20">
+ <el-col :span="8" v-for="template in configTemplates" :key="template.name">
+ <el-card class="template-item" @click="loadTemplate(template)">
+ <h5>{{ template.name }}</h5>
+ <p>{{ template.description }}</p>
+ </el-card>
+ </el-col>
+ </el-row>
+ </el-card>
+
+ <!-- 保存历史记录 -->
+ <el-card v-if="saveHistory.length > 0" class="history-card">
+ <template #header>
+ <h4>保存历史</h4>
+ </template>
+ <el-table :data="saveHistory" border style="width: 100%">
+ <el-table-column prop="kind" label="类型" width="150" />
+ <el-table-column prop="page" label="页面" width="150" />
+ <el-table-column prop="configData" label="配置数据" min-width="200" show-overflow-tooltip />
+ <el-table-column prop="saveTime" label="保存时间" width="160" />
+ <el-table-column label="操作" width="120">
+ <template #default="scope">
+ <el-button type="primary" size="small" @click="loadHistory(scope.row)">载入</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+ </el-card>
+ </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive } from 'vue';
+import { request } from '@core/utils/request';
+import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
+
+// 定义接口类型
+interface SetConfigParams {
+ kind?: string;
+ page?: string;
+ configData?: string;
+}
+
+interface HistoryItem extends SetConfigParams {
+ saveTime: string;
+}
+
+interface ConfigTemplate {
+ name: string;
+ description: string;
+ kind: string;
+ page: string;
+ data: string;
+}
+
+// 表单引用
+const formRef = ref<FormInstance | null>(null);
+const saving = ref(false);
+
+// 表单数据
+const configForm = reactive<SetConfigParams>({
+ kind: '',
+ page: '',
+ configData: '',
+});
+
+// 保存历史
+const saveHistory = ref<HistoryItem[]>([]);
+
+// 配置模板
+const configTemplates = ref<ConfigTemplate[]>([
+ {
+ name: '表格配置',
+ description: '数据表格的基本配置',
+ kind: 'table',
+ page: 'list',
+ data: JSON.stringify({
+ columns: [
+ { prop: 'id', label: 'ID', width: 80 },
+ { prop: 'name', label: '名称', minWidth: 150 },
+ { prop: 'status', label: '状态', width: 100 }
+ ],
+ pagination: {
+ pageSize: 20,
+ pageSizes: [10, 20, 50, 100]
+ }
+ }, null, 2)
+ },
+ {
+ name: '表单配置',
+ description: '表单字段的配置',
+ kind: 'form',
+ page: 'edit',
+ data: JSON.stringify({
+ fields: [
+ { name: 'name', label: '名称', type: 'input', required: true },
+ { name: 'email', label: '邮箱', type: 'input', required: true },
+ { name: 'status', label: '状态', type: 'switch', required: false }
+ ],
+ layout: {
+ labelWidth: '100px',
+ size: 'default'
+ }
+ }, null, 2)
+ },
+ {
+ name: '菜单配置',
+ description: '页面菜单的配置',
+ kind: 'menu',
+ page: 'navigation',
+ data: JSON.stringify({
+ items: [
+ { name: '首页', path: '/home', icon: 'home' },
+ { name: '用户管理', path: '/users', icon: 'user' },
+ { name: '设置', path: '/settings', icon: 'setting' }
+ ],
+ style: {
+ theme: 'dark',
+ collapsed: false
+ }
+ }, null, 2)
+ }
+]);
+
+// 表单验证规则
+const formRules = reactive<FormRules>({
+ kind: [
+ { required: true, message: '请输入配置类型', trigger: 'blur' },
+ ],
+ page: [
+ { required: true, message: '请输入页面名称', trigger: 'blur' },
+ ],
+ configData: [
+ { required: true, message: '请输入配置数据', trigger: 'blur' },
+ ],
+});
+
+// 设置配置
+const handleSetConfig = async () => {
+ if (!formRef.value) return;
+
+ formRef.value.validate(async (valid: boolean) => {
+ if (valid) {
+ // 验证JSON格式
+ try {
+ JSON.parse(configForm.configData || '{}');
+ } catch {
+ ElMessage.error('配置数据不是有效的JSON格式');
+ return;
+ }
+
+ saving.value = true;
+ try {
+ await request.post('/Cube/SetPageConfig', JSON.parse(configForm.configData || '{}'), {
+ params: {
+ kind: configForm.kind,
+ page: configForm.page,
+ },
+ });
+
+ ElMessage.success('配置保存成功');
+
+ // 添加到历史记录
+ saveHistory.value.unshift({
+ ...configForm,
+ saveTime: new Date().toLocaleString(),
+ });
+
+ // 保持历史记录在10条以内
+ if (saveHistory.value.length > 10) {
+ saveHistory.value = saveHistory.value.slice(0, 10);
+ }
+
+ } catch (error) {
+ ElMessage.error('配置保存失败');
+ console.error('Set page config error:', error);
+ } finally {
+ saving.value = false;
+ }
+ }
+ });
+};
+
+// 重置表单
+const resetForm = () => {
+ if (formRef.value) {
+ formRef.value.resetFields();
+ }
+ Object.assign(configForm, {
+ kind: '',
+ page: '',
+ configData: '',
+ });
+};
+
+// 格式化JSON
+const formatJson = () => {
+ if (configForm.configData) {
+ try {
+ const parsed = JSON.parse(configForm.configData);
+ configForm.configData = JSON.stringify(parsed, null, 2);
+ ElMessage.success('JSON格式化成功');
+ } catch {
+ ElMessage.error('JSON格式不正确');
+ }
+ }
+};
+
+// 验证JSON
+const validateJson = () => {
+ if (!configForm.configData) {
+ ElMessage.warning('请先输入配置数据');
+ return;
+ }
+
+ try {
+ JSON.parse(configForm.configData);
+ ElMessage.success('JSON格式验证通过');
+ } catch {
+ ElMessage.error('JSON格式验证失败');
+ }
+};
+
+// 载入模板
+const loadTemplate = (template: ConfigTemplate) => {
+ Object.assign(configForm, {
+ kind: template.kind,
+ page: template.page,
+ configData: template.data,
+ });
+ ElMessage.success(`模板 "${template.name}" 已载入`);
+};
+
+// 载入历史记录
+const loadHistory = (item: HistoryItem) => {
+ Object.assign(configForm, {
+ kind: item.kind,
+ page: item.page,
+ configData: item.configData,
+ });
+ ElMessage.success('历史记录已载入');
+};
+</script>
+
+<style scoped>
+.set-page-config-container {
+ padding: 20px;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.config-form {
+ max-width: 800px;
+}
+
+.template-card {
+ margin-top: 20px;
+}
+
+.template-item {
+ cursor: pointer;
+ transition: all 0.3s;
+ margin-bottom: 10px;
+}
+
+.template-item:hover {
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+ transform: translateY(-2px);
+}
+
+.template-item h5 {
+ margin: 0 0 8px 0;
+ color: #409eff;
+}
+
+.template-item p {
+ margin: 0;
+ color: #666;
+ font-size: 12px;
+}
+
+.history-card {
+ margin-top: 20px;
+}
+</style>
diff --git a/apps/cube-v1/src/pages/cube/user-search/index.vue b/apps/cube-v1/src/pages/cube/user-search/index.vue
new file mode 100644
index 0000000..97e2db1
--- /dev/null
+++ b/apps/cube-v1/src/pages/cube/user-search/index.vue
@@ -0,0 +1,190 @@
+<template>
+ <div class="cube-user-search-container">
+ <el-card class="box-card">
+ <template #header>
+ <div class="card-header">
+ <h3>用户搜索</h3>
+ <el-button type="primary" @click="handleSearch">搜索用户</el-button>
+ </div>
+ </template>
+
+ <el-form :inline="true" :model="searchForm" class="search-form">
+ <el-form-item label="角色ID">
+ <el-input-number v-model="searchForm.roleId" :min="0" placeholder="角色ID" />
+ </el-form-item>
+ <el-form-item label="部门ID">
+ <el-input-number v-model="searchForm.departmentId" :min="0" placeholder="部门ID" />
+ </el-form-item>
+ <el-form-item label="关键字">
+ <el-input v-model="searchForm.key" placeholder="请输入关键字" clearable />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleSearch">查询</el-button>
+ <el-button @click="resetSearch">重置</el-button>
+ </el-form-item>
+ </el-form>
+
+ <el-table :data="tableData" border style="width: 100%" v-loading="loading">
+ <el-table-column prop="id" label="ID" width="80" />
+ <el-table-column prop="name" label="用户名" min-width="120" />
+ <el-table-column prop="displayName" label="显示名称" min-width="120" />
+ <el-table-column prop="email" label="邮箱" min-width="150" />
+ <el-table-column prop="mobile" label="手机号" min-width="120" />
+ <el-table-column prop="roleId" label="角色ID" width="80" />
+ <el-table-column prop="departmentId" label="部门ID" width="80" />
+ <el-table-column prop="createTime" label="创建时间" width="160" />
+ <el-table-column label="状态" width="80">
+ <template #default="scope">
+ <el-tag :type="scope.row.enable ? 'success' : 'danger'">
+ {{ scope.row.enable ? '启用' : '禁用' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <div class="pagination">
+ <el-pagination
+ v-model:current-page="currentPage"
+ v-model:page-size="pageSize"
+ :page-sizes="[10, 20, 50, 100]"
+ :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ @size-change="handleSizeChange"
+ @current-change="handleCurrentChange"
+ />
+ </div>
+ </el-card>
+ </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, onMounted } from 'vue';
+import { request } from '@core/utils/request';
+
+// 定义搜索参数类型
+interface UserSearchParams {
+ roleId: number;
+ departmentId: number;
+ key: string;
+}
+
+// 定义用户数据类型
+interface UserData {
+ id: number;
+ name: string;
+ displayName: string;
+ email: string;
+ mobile: string;
+ roleId: number;
+ departmentId: number;
+ createTime: string;
+ enable: boolean;
+}
+
+// 表格数据
+const tableData = ref<UserData[]>([]);
+const loading = ref(false);
+const total = ref(0);
+const currentPage = ref(1);
+const pageSize = ref(10);
+
+// 查询表单
+const searchForm = reactive<UserSearchParams>({
+ roleId: 0,
+ departmentId: 0,
+ key: '',
+});
+
+// 加载数据
+const loadData = async () => {
+ loading.value = true;
+ try {
+ const response = await request.get('/Cube/UserSearch', {
+ params: {
+ roleId: searchForm.roleId || undefined,
+ departmentId: searchForm.departmentId || undefined,
+ key: searchForm.key || undefined,
+ },
+ });
+
+ // 处理响应数据
+ if (Array.isArray(response)) {
+ tableData.value = response.map((item, index) => ({
+ id: item.id || index + 1,
+ name: item.name || `用户${index + 1}`,
+ displayName: item.displayName || item.name || `用户${index + 1}`,
+ email: item.email || item.mail || '',
+ mobile: item.mobile || item.phone || '',
+ roleId: item.roleId || item.roleID || 0,
+ departmentId: item.departmentId || item.departmentID || 0,
+ createTime: item.createTime || new Date().toLocaleString(),
+ enable: item.enable !== false,
+ }));
+ total.value = response.length;
+ } else {
+ tableData.value = [];
+ total.value = 0;
+ }
+ } catch {
+ tableData.value = [];
+ total.value = 0;
+ } finally {
+ loading.value = false;
+ }
+};
+
+// 页码变更处理
+const handleCurrentChange = (page: number) => {
+ currentPage.value = page;
+ loadData();
+};
+
+// 每页显示条数变更处理
+const handleSizeChange = (size: number) => {
+ pageSize.value = size;
+ currentPage.value = 1;
+ loadData();
+};
+
+// 搜索
+const handleSearch = () => {
+ currentPage.value = 1;
+ loadData();
+};
+
+// 重置搜索
+const resetSearch = () => {
+ searchForm.roleId = 0;
+ searchForm.departmentId = 0;
+ searchForm.key = '';
+ currentPage.value = 1;
+ loadData();
+};
+
+// 初始化加载数据
+onMounted(() => {
+ loadData();
+});
+</script>
+
+<style scoped>
+.cube-user-search-container {
+ padding: 20px;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.search-form {
+ margin-bottom: 20px;
+}
+
+.pagination {
+ margin-top: 20px;
+ display: flex;
+ justify-content: flex-end;
+}
+</style>
diff --git a/apps/cube-v1/src/pages/home/index.vue b/apps/cube-v1/src/pages/home/index.vue
new file mode 100644
index 0000000..6456098
--- /dev/null
+++ b/apps/cube-v1/src/pages/home/index.vue
@@ -0,0 +1,252 @@
+<template>
+ <div class="home-container">
+ <el-card class="welcome-card">
+ <template #header>
+ <h1>Cube V1 API 管理系统</h1>
+ <p>基于 v1_OpenAPI.json 自动生成的 API 管理界面</p>
+ </template>
+
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-card class="api-group-card">
+ <template #header>
+ <h3>🎯 Cube API</h3>
+ </template>
+ <div class="api-list">
+ <el-button
+ v-for="api in cubeApis"
+ :key="api.name"
+ type="primary"
+ @click="navigateToApi(api.path)"
+ class="api-button"
+ >
+ {{ api.name }}
+ </el-button>
+ </div>
+ </el-card>
+ </el-col>
+
+ <el-col :span="12">
+ <el-card class="api-group-card">
+ <template #header>
+ <h3>🔐 SSO API</h3>
+ </template>
+ <div class="api-list">
+ <el-button
+ v-for="api in ssoApis"
+ :key="api.name"
+ type="success"
+ @click="navigateToApi(api.path)"
+ class="api-button"
+ >
+ {{ api.name }}
+ </el-button>
+ </div>
+ </el-card>
+ </el-col>
+ </el-row>
+ </el-card>
+
+ <!-- 统计信息 -->
+ <el-card class="stats-card">
+ <template #header>
+ <h3>📊 统计信息</h3>
+ </template>
+ <el-row :gutter="20">
+ <el-col :span="6">
+ <el-statistic title="Cube API 数量" :value="cubeApis.length" />
+ </el-col>
+ <el-col :span="6">
+ <el-statistic title="SSO API 数量" :value="ssoApis.length" />
+ </el-col>
+ <el-col :span="6">
+ <el-statistic title="总 API 数量" :value="cubeApis.length + ssoApis.length" />
+ </el-col>
+ <el-col :span="6">
+ <el-statistic title="页面数量" :value="cubeApis.length + ssoApis.length" />
+ </el-col>
+ </el-row>
+ </el-card>
+
+ <!-- 最近访问 -->
+ <el-card class="recent-card" v-if="recentVisited.length > 0">
+ <template #header>
+ <h3>🕒 最近访问</h3>
+ </template>
+ <div class="recent-list">
+ <el-tag
+ v-for="item in recentVisited"
+ :key="item.path"
+ @click="navigateToApi(item.path)"
+ class="recent-tag"
+ type="info"
+ >
+ {{ item.name }}
+ </el-tag>
+ </div>
+ </el-card>
+ </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive } from 'vue';
+import { useRouter } from 'vue-router';
+
+interface ApiItem {
+ name: string;
+ path: string;
+ method: string;
+ description: string;
+}
+
+const router = useRouter();
+
+// Cube API 列表
+const cubeApis = reactive<ApiItem[]>([
+ { name: 'Cube 信息', path: '/Cube/Info', method: 'GET', description: '获取 Cube 系统信息' },
+ { name: 'API 列表', path: '/Cube/Apis', method: 'GET', description: '获取所有可用的 API 列表' },
+ { name: '用户搜索', path: '/Cube/UserSearch', method: 'GET', description: '搜索用户信息' },
+ { name: '部门搜索', path: '/Cube/DepartmentSearch', method: 'GET', description: '搜索部门信息' },
+ { name: '获取区域', path: '/Cube/GetArea', method: 'GET', description: '获取指定区域信息' },
+ { name: '区域子节点', path: '/Cube/AreaChilds', method: 'GET', description: '获取区域的子节点' },
+ { name: '区域父节点', path: '/Cube/AreaParents', method: 'GET', description: '获取区域的父节点' },
+ { name: '区域所有父节点', path: '/Cube/AreaAllParents', method: 'GET', description: '获取区域的所有父节点' },
+ { name: '头像管理', path: '/Cube/Avatar', method: 'GET', description: '获取用户头像' },
+ { name: '查找配置', path: '/Cube/Lookup', method: 'GET', description: '查找系统配置' },
+ { name: '保存布局', path: '/Cube/SaveLayout', method: 'POST', description: '保存页面布局配置' },
+ { name: '获取页面配置', path: '/Cube/GetPageConfig', method: 'GET', description: '获取页面配置' },
+ { name: '设置页面配置', path: '/Cube/SetPageConfig', method: 'POST', description: '设置页面配置' },
+ { name: '图片管理', path: '/Cube/Image', method: 'GET', description: '图片资源管理' },
+ { name: '文件管理', path: '/Cube/File', method: 'GET', description: '文件资源管理' },
+]);
+
+// SSO API 列表
+const ssoApis = reactive<ApiItem[]>([
+ { name: 'SSO 登录', path: '/Sso/Login', method: 'GET', description: 'SSO 登录入口' },
+ { name: '登录信息', path: '/Sso/LoginInfo', method: 'GET', description: '获取登录信息' },
+ { name: 'SSO 登出', path: '/Sso/Logout', method: 'GET', description: 'SSO 登出' },
+ { name: '绑定账户', path: '/Sso/Bind', method: 'GET', description: '绑定 SSO 账户' },
+ { name: '解绑账户', path: '/Sso/UnBind', method: 'GET', description: '解绑 SSO 账户' },
+ { name: '授权认证', path: '/Sso/Authorize', method: 'GET', description: 'OAuth 授权认证' },
+ { name: 'Auth2 认证', path: '/Sso/Auth2', method: 'GET', description: 'Auth2 认证' },
+ { name: 'Access Token', path: '/Sso/Access_Token', method: 'GET/POST', description: '获取访问令牌' },
+ { name: 'Token 管理', path: '/Sso/Token', method: 'GET/POST', description: 'Token 管理' },
+ { name: '密码令牌', path: '/Sso/PasswordToken', method: 'GET/POST', description: '密码令牌管理' },
+ { name: '用户信息', path: '/Sso/UserInfo', method: 'GET', description: '获取用户信息' },
+ { name: '刷新令牌', path: '/Sso/Refresh_Token', method: 'GET/POST', description: '刷新访问令牌' },
+ { name: '认证验证', path: '/Sso/Auth', method: 'GET', description: '认证验证' },
+ { name: '获取密钥', path: '/Sso/GetKey', method: 'GET', description: '获取加密密钥' },
+ { name: '验证令牌', path: '/Sso/Verify', method: 'GET', description: '验证令牌有效性' },
+ { name: '用户认证', path: '/Sso/UserAuth', method: 'GET/POST', description: '用户认证' },
+ { name: 'SSO 头像', path: '/Sso/Avatar', method: 'GET', description: 'SSO 用户头像' },
+]);
+
+// 最近访问记录
+const recentVisited = ref<ApiItem[]>([]);
+
+// 导航到 API 页面
+const navigateToApi = (path: string) => {
+ // 找到对应的 API 信息
+ const api = [...cubeApis, ...ssoApis].find(item => item.path === path);
+ if (api) {
+ // 添加到最近访问记录
+ const existingIndex = recentVisited.value.findIndex(item => item.path === path);
+ if (existingIndex > -1) {
+ recentVisited.value.splice(existingIndex, 1);
+ }
+ recentVisited.value.unshift(api);
+
+ // 限制最近访问记录数量
+ if (recentVisited.value.length > 10) {
+ recentVisited.value = recentVisited.value.slice(0, 10);
+ }
+ }
+
+ // 导航到页面
+ router.push(path);
+};
+</script>
+
+<style scoped>
+.home-container {
+ padding: 20px;
+ max-width: 1200px;
+ margin: 0 auto;
+}
+
+.welcome-card {
+ margin-bottom: 20px;
+}
+
+.welcome-card h1 {
+ color: #409eff;
+ margin-bottom: 10px;
+}
+
+.welcome-card p {
+ color: #666;
+ margin: 0;
+}
+
+.api-group-card {
+ height: 400px;
+ margin-bottom: 20px;
+}
+
+.api-list {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ max-height: 320px;
+ overflow-y: auto;
+}
+
+.api-button {
+ width: 100%;
+ justify-content: flex-start;
+ text-align: left;
+}
+
+.stats-card {
+ margin-bottom: 20px;
+}
+
+.recent-card {
+ margin-bottom: 20px;
+}
+
+.recent-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+}
+
+.recent-tag {
+ cursor: pointer;
+ transition: all 0.3s;
+}
+
+.recent-tag:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+}
+
+/* 滚动条样式 */
+.api-list::-webkit-scrollbar {
+ width: 6px;
+}
+
+.api-list::-webkit-scrollbar-track {
+ background: #f1f1f1;
+ border-radius: 3px;
+}
+
+.api-list::-webkit-scrollbar-thumb {
+ background: #c1c1c1;
+ border-radius: 3px;
+}
+
+.api-list::-webkit-scrollbar-thumb:hover {
+ background: #a8a8a8;
+}
+</style>
diff --git a/apps/cube-v1/src/pages/sso/access-token/index.vue b/apps/cube-v1/src/pages/sso/access-token/index.vue
new file mode 100644
index 0000000..c3f7709
--- /dev/null
+++ b/apps/cube-v1/src/pages/sso/access-token/index.vue
@@ -0,0 +1,375 @@
+<template>
+ <div class="sso-access-token-container">
+ <el-card class="box-card">
+ <template #header>
+ <div class="card-header">
+ <h3>获取 Access Token</h3>
+ <el-button type="primary" @click="handleGetAccessToken">获取 Token</el-button>
+ </div>
+ </template>
+
+ <el-tabs v-model="activeTab" class="token-tabs">
+ <!-- GET 请求 Tab -->
+ <el-tab-pane label="GET 请求" name="get">
+ <el-form :inline="true" :model="getForm" class="search-form">
+ <el-form-item label="Client ID">
+ <el-input v-model="getForm.client_id" placeholder="请输入Client ID" clearable />
+ </el-form-item>
+ <el-form-item label="Client Secret">
+ <el-input v-model="getForm.client_secret" placeholder="请输入Client Secret" clearable />
+ </el-form-item>
+ <el-form-item label="Code">
+ <el-input v-model="getForm.code" placeholder="请输入授权码" clearable />
+ </el-form-item>
+ <el-form-item label="Grant Type">
+ <el-select v-model="getForm.grant_type" placeholder="请选择Grant Type">
+ <el-option label="authorization_code" value="authorization_code" />
+ <el-option label="refresh_token" value="refresh_token" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleGetRequest">GET 请求</el-button>
+ <el-button @click="resetGetForm">重置</el-button>
+ </el-form-item>
+ </el-form>
+ </el-tab-pane>
+
+ <!-- POST 请求 Tab -->
+ <el-tab-pane label="POST 请求" name="post">
+ <el-form :model="postForm" label-width="120px" class="post-form">
+ <el-form-item label="Client ID">
+ <el-input v-model="postForm.client_id" placeholder="请输入Client ID" />
+ </el-form-item>
+ <el-form-item label="Client Secret">
+ <el-input v-model="postForm.client_secret" placeholder="请输入Client Secret" />
+ </el-form-item>
+ <el-form-item label="Code">
+ <el-input v-model="postForm.code" placeholder="请输入授权码" />
+ </el-form-item>
+ <el-form-item label="Grant Type">
+ <el-select v-model="postForm.grant_type" placeholder="请选择Grant Type">
+ <el-option label="authorization_code" value="authorization_code" />
+ <el-option label="refresh_token" value="refresh_token" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handlePostRequest">POST 请求</el-button>
+ <el-button @click="resetPostForm">重置</el-button>
+ </el-form-item>
+ </el-form>
+ </el-tab-pane>
+ </el-tabs>
+
+ <!-- Token 结果展示 -->
+ <el-card v-if="tokenResult" class="result-card">
+ <template #header>
+ <h4>Token 结果</h4>
+ </template>
+ <el-descriptions :column="1" border>
+ <el-descriptions-item label="Access Token">
+ <div class="token-display">
+ <el-input v-model="tokenResult.access_token" readonly />
+ <el-button
+ type="primary"
+ size="small"
+ @click="copyToken(tokenResult.access_token)"
+ class="copy-btn"
+ >
+ 复制
+ </el-button>
+ </div>
+ </el-descriptions-item>
+ <el-descriptions-item label="Token Type">
+ {{ tokenResult.token_type || 'Bearer' }}
+ </el-descriptions-item>
+ <el-descriptions-item label="Expires In">
+ {{ tokenResult.expires_in ? `${tokenResult.expires_in} 秒` : '未知' }}
+ </el-descriptions-item>
+ <el-descriptions-item label="Refresh Token" v-if="tokenResult.refresh_token">
+ <div class="token-display">
+ <el-input v-model="tokenResult.refresh_token" readonly />
+ <el-button
+ type="success"
+ size="small"
+ @click="copyToken(tokenResult.refresh_token)"
+ class="copy-btn"
+ >
+ 复制
+ </el-button>
+ </div>
+ </el-descriptions-item>
+ <el-descriptions-item label="Scope">
+ {{ tokenResult.scope || '未指定' }}
+ </el-descriptions-item>
+ <el-descriptions-item label="获取时间">
+ {{ tokenResult.timestamp }}
+ </el-descriptions-item>
+ </el-descriptions>
+ </el-card>
+
+ <!-- Token 历史记录 -->
+ <el-card v-if="tokenHistory.length > 0" class="history-card">
+ <template #header>
+ <h4>Token 历史</h4>
+ </template>
+ <el-table :data="tokenHistory" border style="width: 100%">
+ <el-table-column prop="client_id" label="Client ID" width="150" show-overflow-tooltip />
+ <el-table-column prop="grant_type" label="Grant Type" width="150" />
+ <el-table-column prop="access_token" label="Access Token" min-width="200" show-overflow-tooltip />
+ <el-table-column prop="expires_in" label="过期时间" width="100" />
+ <el-table-column prop="timestamp" label="获取时间" width="160" />
+ <el-table-column label="操作" width="120">
+ <template #default="scope">
+ <el-button type="primary" size="small" @click="useToken(scope.row)">使用</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+ </el-card>
+ </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive } from 'vue';
+import { request } from '@core/utils/request';
+import { ElMessage } from 'element-plus';
+
+// 定义接口类型
+interface AccessTokenParams {
+ client_id?: string;
+ client_secret?: string;
+ code?: string;
+ grant_type?: string;
+}
+
+interface TokenResult {
+ access_token?: string;
+ token_type?: string;
+ expires_in?: number;
+ refresh_token?: string;
+ scope?: string;
+ timestamp: string;
+}
+
+interface TokenHistoryItem extends TokenResult, AccessTokenParams {}
+
+// 当前激活的 Tab
+const activeTab = ref('get');
+
+// GET 请求表单
+const getForm = reactive<AccessTokenParams>({
+ client_id: '',
+ client_secret: '',
+ code: '',
+ grant_type: 'authorization_code',
+});
+
+// POST 请求表单
+const postForm = reactive<AccessTokenParams>({
+ client_id: '',
+ client_secret: '',
+ code: '',
+ grant_type: 'authorization_code',
+});
+
+// Token 结果
+const tokenResult = ref<TokenResult | null>(null);
+const tokenHistory = ref<TokenHistoryItem[]>([]);
+
+// 处理 GET 请求
+const handleGetRequest = async () => {
+ if (!getForm.client_id || !getForm.client_secret) {
+ ElMessage.warning('请输入 Client ID 和 Client Secret');
+ return;
+ }
+
+ try {
+ const response = await request.get('/Sso/Access_Token', {
+ params: getForm,
+ });
+
+ const result: TokenResult = {
+ access_token: response.access_token,
+ token_type: response.token_type,
+ expires_in: response.expires_in,
+ refresh_token: response.refresh_token,
+ scope: response.scope,
+ timestamp: new Date().toLocaleString(),
+ };
+
+ tokenResult.value = result;
+
+ // 添加到历史记录
+ tokenHistory.value.unshift({
+ ...result,
+ ...getForm,
+ });
+
+ // 保持历史记录在10条以内
+ if (tokenHistory.value.length > 10) {
+ tokenHistory.value = tokenHistory.value.slice(0, 10);
+ }
+
+ ElMessage.success('Access Token 获取成功');
+ } catch {
+ ElMessage.error('Access Token 获取失败');
+ }
+};
+
+// 处理 POST 请求
+const handlePostRequest = async () => {
+ if (!postForm.client_id || !postForm.client_secret) {
+ ElMessage.warning('请输入 Client ID 和 Client Secret');
+ return;
+ }
+
+ try {
+ const response = await request.post('/Sso/Access_Token', null, {
+ params: postForm,
+ });
+
+ const result: TokenResult = {
+ access_token: response.access_token,
+ token_type: response.token_type,
+ expires_in: response.expires_in,
+ refresh_token: response.refresh_token,
+ scope: response.scope,
+ timestamp: new Date().toLocaleString(),
+ };
+
+ tokenResult.value = result;
+
+ // 添加到历史记录
+ tokenHistory.value.unshift({
+ ...result,
+ ...postForm,
+ });
+
+ // 保持历史记录在10条以内
+ if (tokenHistory.value.length > 10) {
+ tokenHistory.value = tokenHistory.value.slice(0, 10);
+ }
+
+ ElMessage.success('Access Token 获取成功');
+ } catch {
+ ElMessage.error('Access Token 获取失败');
+ }
+};
+
+// 获取 Access Token(通用方法)
+const handleGetAccessToken = () => {
+ if (activeTab.value === 'get') {
+ handleGetRequest();
+ } else {
+ handlePostRequest();
+ }
+};
+
+// 复制 Token
+const copyToken = async (token?: string) => {
+ if (!token) {
+ ElMessage.warning('Token 为空');
+ return;
+ }
+
+ try {
+ await navigator.clipboard.writeText(token);
+ ElMessage.success('Token 已复制到剪贴板');
+ } catch {
+ ElMessage.error('复制失败,请手动复制');
+ }
+};
+
+// 使用 Token(从历史记录中恢复)
+const useToken = (item: TokenHistoryItem) => {
+ tokenResult.value = {
+ access_token: item.access_token,
+ token_type: item.token_type,
+ expires_in: item.expires_in,
+ refresh_token: item.refresh_token,
+ scope: item.scope,
+ timestamp: item.timestamp,
+ };
+
+ // 同时恢复表单数据
+ if (activeTab.value === 'get') {
+ Object.assign(getForm, {
+ client_id: item.client_id,
+ client_secret: item.client_secret,
+ code: item.code,
+ grant_type: item.grant_type,
+ });
+ } else {
+ Object.assign(postForm, {
+ client_id: item.client_id,
+ client_secret: item.client_secret,
+ code: item.code,
+ grant_type: item.grant_type,
+ });
+ }
+
+ ElMessage.success('Token 信息已恢复');
+};
+
+// 重置 GET 表单
+const resetGetForm = () => {
+ Object.assign(getForm, {
+ client_id: '',
+ client_secret: '',
+ code: '',
+ grant_type: 'authorization_code',
+ });
+};
+
+// 重置 POST 表单
+const resetPostForm = () => {
+ Object.assign(postForm, {
+ client_id: '',
+ client_secret: '',
+ code: '',
+ grant_type: 'authorization_code',
+ });
+};
+</script>
+
+<style scoped>
+.sso-access-token-container {
+ padding: 20px;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.token-tabs {
+ margin-bottom: 20px;
+}
+
+.search-form {
+ margin-bottom: 20px;
+}
+
+.post-form {
+ max-width: 600px;
+}
+
+.result-card {
+ margin-top: 20px;
+}
+
+.token-display {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.copy-btn {
+ flex-shrink: 0;
+}
+
+.history-card {
+ margin-top: 20px;
+}
+</style>
diff --git a/apps/cube-v1/src/pages/sso/login/index.vue b/apps/cube-v1/src/pages/sso/login/index.vue
new file mode 100644
index 0000000..69faa8a
--- /dev/null
+++ b/apps/cube-v1/src/pages/sso/login/index.vue
@@ -0,0 +1,165 @@
+<template>
+ <div class="sso-login-container">
+ <el-card class="box-card">
+ <template #header>
+ <div class="card-header">
+ <h3>SSO 登录</h3>
+ <el-button type="primary" @click="handleLogin">执行登录</el-button>
+ </div>
+ </template>
+
+ <el-form :inline="true" :model="loginForm" class="search-form">
+ <el-form-item label="登录名">
+ <el-input v-model="loginForm.name" placeholder="请输入登录名" clearable />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleLogin">登录</el-button>
+ <el-button @click="resetForm">重置</el-button>
+ </el-form-item>
+ </el-form>
+
+ <div v-if="loginResult" class="login-result">
+ <el-alert
+ :title="loginResult.success ? '登录成功' : '登录失败'"
+ :type="loginResult.success ? 'success' : 'error'"
+ :description="loginResult.message"
+ show-icon
+ :closable="false"
+ />
+
+ <el-table v-if="loginResult.data" :data="[loginResult.data]" border style="width: 100%; margin-top: 20px;">
+ <el-table-column prop="key" label="字段" width="150" />
+ <el-table-column prop="value" label="值" />
+ </el-table>
+ </div>
+
+ <div v-if="loading" class="loading">
+ <el-loading-directive v-loading="loading" />
+ </div>
+ </el-card>
+ </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive } from 'vue';
+import { request } from '@core/utils/request';
+
+// 定义登录参数类型
+interface SsoLoginParams {
+ name: string;
+}
+
+// 定义登录结果类型
+interface LoginResult {
+ success: boolean;
+ message: string;
+ data?: Record<string, unknown>;
+}
+
+// 登录表单
+const loginForm = reactive<SsoLoginParams>({
+ name: '',
+});
+
+// 状态
+const loading = ref(false);
+const loginResult = ref<LoginResult | null>(null);
+
+// 执行登录
+const handleLogin = async () => {
+ if (!loginForm.name) {
+ loginResult.value = {
+ success: false,
+ message: '请输入登录名',
+ };
+ return;
+ }
+
+ loading.value = true;
+ loginResult.value = null;
+
+ try {
+ const response = await request.get('/Sso/Login', {
+ params: {
+ name: loginForm.name,
+ },
+ });
+
+ // 处理响应数据
+ if (response) {
+ loginResult.value = {
+ success: true,
+ message: '登录请求成功',
+ data: flattenObject(response),
+ };
+ } else {
+ loginResult.value = {
+ success: false,
+ message: '登录响应为空',
+ };
+ }
+ } catch (error) {
+ loginResult.value = {
+ success: false,
+ message: `登录失败: ${error}`,
+ };
+ } finally {
+ loading.value = false;
+ }
+};
+
+// 重置表单
+const resetForm = () => {
+ loginForm.name = '';
+ loginResult.value = null;
+};
+
+// 扁平化对象用于表格显示
+const flattenObject = (obj: Record<string, unknown>): Array<{key: string, value: string}> => {
+ const result: Array<{key: string, value: string}> = [];
+
+ const flatten = (current: Record<string, unknown>, prefix = '') => {
+ Object.keys(current).forEach(key => {
+ const value = current[key];
+ const newKey = prefix ? `${prefix}.${key}` : key;
+
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
+ flatten(value as Record<string, unknown>, newKey);
+ } else {
+ result.push({
+ key: newKey,
+ value: String(value),
+ });
+ }
+ });
+ };
+
+ flatten(obj);
+ return result;
+};
+</script>
+
+<style scoped>
+.sso-login-container {
+ padding: 20px;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.search-form {
+ margin-bottom: 20px;
+}
+
+.login-result {
+ margin-top: 20px;
+}
+
+.loading {
+ text-align: center;
+ padding: 20px;
+}
+</style>
diff --git a/apps/cube-v1/src/pages/sso/token/index.vue b/apps/cube-v1/src/pages/sso/token/index.vue
new file mode 100644
index 0000000..882631f
--- /dev/null
+++ b/apps/cube-v1/src/pages/sso/token/index.vue
@@ -0,0 +1,239 @@
+<template>
+ <div class="sso-token-container">
+ <el-card class="box-card">
+ <template #header>
+ <div class="card-header">
+ <h3>SSO Token</h3>
+ <el-button type="primary" @click="handleGetToken">获取Token</el-button>
+ </div>
+ </template>
+
+ <el-form :model="tokenForm" :rules="tokenRules" ref="tokenFormRef" label-width="120px">
+ <el-form-item label="客户端ID" prop="client_id">
+ <el-input v-model="tokenForm.client_id" placeholder="请输入客户端ID" />
+ </el-form-item>
+ <el-form-item label="客户端密钥" prop="client_secret">
+ <el-input v-model="tokenForm.client_secret" type="password" placeholder="请输入客户端密钥" />
+ </el-form-item>
+ <el-form-item label="用户名" prop="username">
+ <el-input v-model="tokenForm.username" placeholder="请输入用户名" />
+ </el-form-item>
+ <el-form-item label="密码" prop="password">
+ <el-input v-model="tokenForm.password" type="password" placeholder="请输入密码" />
+ </el-form-item>
+ <el-form-item label="刷新令牌" prop="refresh_token">
+ <el-input v-model="tokenForm.refresh_token" placeholder="请输入刷新令牌" />
+ </el-form-item>
+ <el-form-item label="授权类型" prop="grant_type">
+ <el-select v-model="tokenForm.grant_type" placeholder="请选择授权类型">
+ <el-option label="password" value="password" />
+ <el-option label="client_credentials" value="client_credentials" />
+ <el-option label="authorization_code" value="authorization_code" />
+ <el-option label="refresh_token" value="refresh_token" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleGetToken" :loading="loading">获取Token</el-button>
+ <el-button @click="handlePostToken" :loading="loading">POST获取Token</el-button>
+ <el-button @click="resetForm">重置</el-button>
+ </el-form-item>
+ </el-form>
+
+ <div v-if="tokenResult" class="token-result">
+ <el-alert
+ :title="tokenResult.success ? '获取Token成功' : '获取Token失败'"
+ :type="tokenResult.success ? 'success' : 'error'"
+ :description="tokenResult.message"
+ show-icon
+ :closable="false"
+ />
+
+ <el-table v-if="tokenResult.data" :data="tokenResult.data" border style="width: 100%; margin-top: 20px;">
+ <el-table-column prop="key" label="字段" width="200" />
+ <el-table-column prop="value" label="值" />
+ </el-table>
+ </div>
+ </el-card>
+ </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive } from 'vue';
+import { request } from '@core/utils/request';
+import type { FormInstance, FormRules } from 'element-plus';
+
+// 定义Token参数类型
+interface SsoTokenParams {
+ client_id: string;
+ client_secret: string;
+ username: string;
+ password: string;
+ refresh_token: string;
+ grant_type: string;
+}
+
+// 定义结果类型
+interface TokenResult {
+ success: boolean;
+ message: string;
+ data?: Array<{key: string, value: string}>;
+}
+
+// 表单引用
+const tokenFormRef = ref<FormInstance | null>(null);
+
+// Token表单
+const tokenForm = reactive<SsoTokenParams>({
+ client_id: '',
+ client_secret: '',
+ username: '',
+ password: '',
+ refresh_token: '',
+ grant_type: 'password',
+});
+
+// 表单验证规则
+const tokenRules = reactive<FormRules>({
+ client_id: [
+ { required: true, message: '请输入客户端ID', trigger: 'blur' }
+ ],
+ client_secret: [
+ { required: true, message: '请输入客户端密钥', trigger: 'blur' }
+ ],
+ grant_type: [
+ { required: true, message: '请选择授权类型', trigger: 'change' }
+ ]
+});
+
+// 状态
+const loading = ref(false);
+const tokenResult = ref<TokenResult | null>(null);
+
+// GET方式获取Token
+const handleGetToken = async () => {
+ if (!tokenFormRef.value) return;
+
+ await tokenFormRef.value.validate(async (valid: boolean) => {
+ if (!valid) return;
+
+ loading.value = true;
+ tokenResult.value = null;
+
+ try {
+ const response = await request.get('/Sso/Token', {
+ params: {
+ client_id: tokenForm.client_id,
+ client_secret: tokenForm.client_secret,
+ username: tokenForm.username || undefined,
+ password: tokenForm.password || undefined,
+ refresh_token: tokenForm.refresh_token || undefined,
+ grant_type: tokenForm.grant_type,
+ },
+ });
+
+ tokenResult.value = {
+ success: true,
+ message: '获取Token成功',
+ data: flattenObject(response),
+ };
+ } catch (error) {
+ tokenResult.value = {
+ success: false,
+ message: `获取Token失败: ${error}`,
+ };
+ } finally {
+ loading.value = false;
+ }
+ });
+};
+
+// POST方式获取Token
+const handlePostToken = async () => {
+ if (!tokenFormRef.value) return;
+
+ await tokenFormRef.value.validate(async (valid: boolean) => {
+ if (!valid) return;
+
+ loading.value = true;
+ tokenResult.value = null;
+
+ try {
+ const response = await request.post('/Sso/Token', null, {
+ params: {
+ client_id: tokenForm.client_id,
+ client_secret: tokenForm.client_secret,
+ username: tokenForm.username || undefined,
+ password: tokenForm.password || undefined,
+ refresh_token: tokenForm.refresh_token || undefined,
+ grant_type: tokenForm.grant_type,
+ },
+ });
+
+ tokenResult.value = {
+ success: true,
+ message: '获取Token成功',
+ data: flattenObject(response),
+ };
+ } catch (error) {
+ tokenResult.value = {
+ success: false,
+ message: `获取Token失败: ${error}`,
+ };
+ } finally {
+ loading.value = false;
+ }
+ });
+};
+
+// 重置表单
+const resetForm = () => {
+ if (tokenFormRef.value) {
+ tokenFormRef.value.resetFields();
+ }
+ tokenResult.value = null;
+};
+
+// 扁平化对象用于表格显示
+const flattenObject = (obj: unknown): Array<{key: string, value: string}> => {
+ const result: Array<{key: string, value: string}> = [];
+
+ if (!obj || typeof obj !== 'object') {
+ return result;
+ }
+
+ const flatten = (current: Record<string, unknown>, prefix = '') => {
+ Object.keys(current).forEach(key => {
+ const value = current[key];
+ const newKey = prefix ? `${prefix}.${key}` : key;
+
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
+ flatten(value as Record<string, unknown>, newKey);
+ } else {
+ result.push({
+ key: newKey,
+ value: String(value),
+ });
+ }
+ });
+ };
+
+ flatten(obj as Record<string, unknown>);
+ return result;
+};
+</script>
+
+<style scoped>
+.sso-token-container {
+ padding: 20px;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.token-result {
+ margin-top: 20px;
+}
+</style>
diff --git a/apps/cube-v1/src/routes/index.ts b/apps/cube-v1/src/routes/index.ts
new file mode 100644
index 0000000..66601ad
--- /dev/null
+++ b/apps/cube-v1/src/routes/index.ts
@@ -0,0 +1,175 @@
+import { type RouteRecordRaw } from 'vue-router';
+
+const routes: RouteRecordRaw[] = [
+ // 首页
+ {
+ path: '/',
+ name: 'home',
+ component: () => import('../pages/home/index.vue'),
+ },
+ // Cube 相关路由 - 只保留接口文档中存在的接口
+ {
+ path: '/Cube/Info',
+ name: 'cube-info',
+ component: () => import('../pages/cube/info/index.vue'),
+ },
+ {
+ path: '/Cube/Apis',
+ name: 'cube-apis',
+ component: () => import('../pages/cube/apis/index.vue'),
+ },
+ {
+ path: '/Cube/UserSearch',
+ name: 'cube-user-search',
+ component: () => import('../pages/cube/user-search/index.vue'),
+ },
+ {
+ path: '/Cube/DepartmentSearch',
+ name: 'cube-department-search',
+ component: () => import('../pages/cube/department-search/index.vue'),
+ },
+ {
+ path: '/Cube/GetArea',
+ name: 'cube-get-area',
+ component: () => import('../pages/cube/get-area/index.vue'),
+ },
+ {
+ path: '/Cube/AreaChilds',
+ name: 'cube-area-childs',
+ component: () => import('../pages/cube/area-childs/index.vue'),
+ },
+ {
+ path: '/Cube/AreaParents',
+ name: 'cube-area-parents',
+ component: () => import('../pages/cube/area-parents/index.vue'),
+ },
+ {
+ path: '/Cube/AreaAllParents',
+ name: 'cube-area-all-parents',
+ component: () => import('../pages/cube/area-all-parents/index.vue'),
+ },
+ {
+ path: '/Cube/Avatar',
+ name: 'cube-avatar',
+ component: () => import('../pages/cube/avatar/index.vue'),
+ },
+ {
+ path: '/Cube/Lookup',
+ name: 'cube-lookup',
+ component: () => import('../pages/cube/lookup/index.vue'),
+ },
+ {
+ path: '/Cube/SaveLayout',
+ name: 'cube-save-layout',
+ component: () => import('../pages/cube/save-layout/index.vue'),
+ },
+ {
+ path: '/Cube/GetPageConfig',
+ name: 'cube-get-page-config',
+ component: () => import('../pages/cube/get-page-config/index.vue'),
+ },
+ {
+ path: '/Cube/SetPageConfig',
+ name: 'cube-set-page-config',
+ component: () => import('../pages/cube/set-page-config/index.vue'),
+ },
+ {
+ path: '/Cube/Image',
+ name: 'cube-image',
+ component: () => import('../pages/cube/image/index.vue'),
+ },
+ {
+ path: '/Cube/File',
+ name: 'cube-file',
+ component: () => import('../pages/cube/file/index.vue'),
+ },
+
+ // SSO 相关路由 - 只保留接口文档中存在的接口
+ {
+ path: '/Sso/Login',
+ name: 'sso-login',
+ component: () => import('../pages/sso/login/index.vue'),
+ },
+ {
+ path: '/Sso/LoginInfo/:id?',
+ name: 'sso-login-info',
+ component: () => import('../pages/sso/login-info/index.vue'),
+ },
+ {
+ path: '/Sso/Logout',
+ name: 'sso-logout',
+ component: () => import('../pages/sso/logout/index.vue'),
+ },
+ {
+ path: '/Sso/Bind',
+ name: 'sso-bind',
+ component: () => import('../pages/sso/bind/index.vue'),
+ },
+ {
+ path: '/Sso/UnBind',
+ name: 'sso-unbind',
+ component: () => import('../pages/sso/unbind/index.vue'),
+ },
+ {
+ path: '/Sso/Authorize',
+ name: 'sso-authorize',
+ component: () => import('../pages/sso/authorize/index.vue'),
+ },
+ {
+ path: '/Sso/Auth2',
+ name: 'sso-auth2',
+ component: () => import('../pages/sso/auth2/index.vue'),
+ },
+ {
+ path: '/Sso/Access_Token',
+ name: 'sso-access-token',
+ component: () => import('../pages/sso/access-token/index.vue'),
+ },
+ {
+ path: '/Sso/Token',
+ name: 'sso-token',
+ component: () => import('../pages/sso/token/index.vue'),
+ },
+ {
+ path: '/Sso/PasswordToken',
+ name: 'sso-password-token',
+ component: () => import('../pages/sso/password-token/index.vue'),
+ },
+ {
+ path: '/Sso/UserInfo',
+ name: 'sso-user-info',
+ component: () => import('../pages/sso/user-info/index.vue'),
+ },
+ {
+ path: '/Sso/Refresh_Token',
+ name: 'sso-refresh-token',
+ component: () => import('../pages/sso/refresh-token/index.vue'),
+ },
+ {
+ path: '/Sso/Auth',
+ name: 'sso-auth',
+ component: () => import('../pages/sso/auth/index.vue'),
+ },
+ {
+ path: '/Sso/GetKey',
+ name: 'sso-get-key',
+ component: () => import('../pages/sso/get-key/index.vue'),
+ },
+ {
+ path: '/Sso/Verify',
+ name: 'sso-verify',
+ component: () => import('../pages/sso/verify/index.vue'),
+ },
+ {
+ path: '/Sso/UserAuth',
+ name: 'sso-user-auth',
+ component: () => import('../pages/sso/user-auth/index.vue'),
+ },
+ {
+ path: '/Sso/Avatar',
+ name: 'sso-avatar',
+ component: () => import('../pages/sso/avatar/index.vue'),
+ },
+];
+
+export default routes;
diff --git a/apps/cube-v1/src/types/index.ts b/apps/cube-v1/src/types/index.ts
new file mode 100644
index 0000000..928cbfd
--- /dev/null
+++ b/apps/cube-v1/src/types/index.ts
@@ -0,0 +1,103 @@
+// v1 API 相关类型定义
+
+// SSO Token 模型
+export interface SsoTokenModel {
+ client_id?: string;
+ client_secret?: string;
+ userName?: string;
+ password?: string;
+ grant_type?: string;
+}
+
+// Cube 相关接口类型
+export interface CubeInfo {
+ state?: string;
+}
+
+export interface UserSearchParams {
+ roleId?: number;
+ departmentId?: number;
+ key?: string;
+}
+
+export interface DepartmentSearchParams {
+ parentid?: number;
+ key?: string;
+}
+
+export interface AreaParams {
+ id?: number;
+ isContainSelf?: boolean;
+}
+
+export interface SaveLayoutParams {
+ userid?: number;
+ category?: string;
+ name?: string;
+ value?: string;
+}
+
+export interface PageConfigParams {
+ kind?: string;
+ page?: string;
+}
+
+// SSO 相关接口类型
+export interface SsoLoginParams {
+ name?: string;
+}
+
+export interface SsoLoginInfoParams {
+ id?: string;
+ code?: string;
+ state?: string;
+}
+
+export interface SsoAuthorizeParams {
+ client_id?: string;
+ redirect_uri?: string;
+ response_type?: string;
+ scope?: string;
+ state?: string;
+ loginUrl?: string;
+}
+
+export interface SsoTokenParams {
+ client_id?: string;
+ client_secret?: string;
+ code?: string;
+ grant_type?: string;
+ username?: string;
+ password?: string;
+ refresh_token?: string;
+}
+
+export interface SsoUserInfoParams {
+ access_token?: string;
+}
+
+export interface SsoAuthParams {
+ access_token?: string;
+ redirect_uri?: string;
+}
+
+export interface SsoVerifyParams {
+ access_token?: string;
+ redirect_uri?: string;
+}
+
+// 通用响应类型
+export interface ApiResponse<T = unknown> {
+ code?: number;
+ message?: string;
+ data?: T;
+ success?: boolean;
+}
+
+// 分页响应类型
+export interface PagedResponse<T = unknown> {
+ data?: T[];
+ total?: number;
+ pageIndex?: number;
+ pageSize?: number;
+}
diff --git a/apps/cube-v1/vite.config.ts b/apps/cube-v1/vite.config.ts
new file mode 100644
index 0000000..043138f
--- /dev/null
+++ b/apps/cube-v1/vite.config.ts
@@ -0,0 +1,16 @@
+import { defineConfig } from 'vite';
+import vue from '@vitejs/plugin-vue';
+import { resolve } from 'path';
+
+export default defineConfig({
+ plugins: [vue()],
+ resolve: {
+ alias: {
+ '@': resolve(__dirname, 'src'),
+ '@core': resolve(__dirname, '../../core'),
+ },
+ },
+ server: {
+ port: 5174,
+ },
+});