|
@@ -0,0 +1,1426 @@
|
|
|
|
+<template>
|
|
|
|
+ <div class="app-platform">
|
|
|
|
+ <!-- 顶部导航栏 -->
|
|
|
|
+ <header class="platform-header">
|
|
|
|
+ <div class="header-left">
|
|
|
|
+ <img src="/static/bb3845c295f66fc0e82f3874da4688e.jpg" alt="logo" class="header-logo">
|
|
|
|
+ <h1>擎云应用平台</h1>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="user-actions">
|
|
|
|
+ <el-dropdown class="user-info" trigger="click">
|
|
|
|
+ <div class="user-avatar-wrapper">
|
|
|
|
+ <img
|
|
|
|
+ ref="avatarImg"
|
|
|
|
+ :src="avatar || require('@/assets/images/profile.jpg')"
|
|
|
|
+ class="user-avatar"
|
|
|
|
+ @error="handleAvatarError"
|
|
|
|
+ >
|
|
|
|
+ <span class="user-name">{{ name }}</span>
|
|
|
|
+ <i class="el-icon-arrow-down" />
|
|
|
|
+ </div>
|
|
|
|
+ <el-dropdown-menu slot="dropdown">
|
|
|
|
+ <!-- <router-link to="/user/profile">
|
|
|
|
+ <el-dropdown-item>
|
|
|
|
+ <i class="el-icon-user" />
|
|
|
|
+ <span>个人中心</span>
|
|
|
|
+ </el-dropdown-item>
|
|
|
|
+ </router-link> -->
|
|
|
|
+ <el-dropdown-item @click.native="handleLogout">
|
|
|
|
+ <i class="el-icon-switch-button" />
|
|
|
|
+ <span>退出登录</span>
|
|
|
|
+ </el-dropdown-item>
|
|
|
|
+ </el-dropdown-menu>
|
|
|
|
+ </el-dropdown>
|
|
|
|
+ </div>
|
|
|
|
+ </header>
|
|
|
|
+
|
|
|
|
+ <!-- 主要内容区域 -->
|
|
|
|
+ <div class="platform-content">
|
|
|
|
+ <!-- 工具栏区域 -->
|
|
|
|
+ <div class="platform-toolbar">
|
|
|
|
+ <div class="toolbar-main">
|
|
|
|
+ <div class="app-categories">
|
|
|
|
+ <button
|
|
|
|
+ :class="['category-btn', { active: currentCategory === 'all' }]"
|
|
|
|
+ @click="currentCategory = 'all'"
|
|
|
|
+ >
|
|
|
|
+ 全部
|
|
|
|
+ </button>
|
|
|
|
+ <button
|
|
|
|
+ v-for="category in categoriesTwo"
|
|
|
|
+ :key="category.id"
|
|
|
|
+ :class="['category-btn', { active: currentCategory === category.id }]"
|
|
|
|
+ @click="currentCategory = category.id"
|
|
|
|
+ >
|
|
|
|
+ {{ category.categoryName }}
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="search-box">
|
|
|
|
+ <el-input
|
|
|
|
+ v-model="searchKeyword"
|
|
|
|
+ placeholder="搜索应用"
|
|
|
|
+ clearable
|
|
|
|
+ @clear="handleSearch"
|
|
|
|
+ @keyup.enter.native="handleSearch"
|
|
|
|
+ >
|
|
|
|
+ <i slot="suffix" class="el-input__icon el-icon-search" @click="handleSearch" />
|
|
|
|
+ </el-input>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 应用卡片网格 -->
|
|
|
|
+ <div class="app-grid">
|
|
|
|
+ <div
|
|
|
|
+ v-for="app in appList"
|
|
|
|
+ :key="app.id"
|
|
|
|
+ class="app-card"
|
|
|
|
+ @click="handleAppClick(app)"
|
|
|
|
+ @dblclick="handleAppDblClick(app)"
|
|
|
|
+ @contextmenu.prevent="handleContextMenu($event, app)"
|
|
|
|
+ >
|
|
|
|
+ <div class="app-icon" :style="{ background: app.iconColor }">
|
|
|
|
+ <i :class="app.iconUrl" />
|
|
|
|
+ </div>
|
|
|
|
+ <h3 class="app-title">{{ app.appName }}</h3>
|
|
|
|
+ <p class="app-desc">{{ app.description }}</p>
|
|
|
|
+ <div class="app-footer">
|
|
|
|
+ <span class="app-date">
|
|
|
|
+ <i class="el-icon-time" />
|
|
|
|
+ {{ app.createTime }}
|
|
|
|
+ </span>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 添加应用卡片 -->
|
|
|
|
+ <div class="add-card" @click="handleAddCardClick">
|
|
|
|
+ <i class="el-icon-plus add-icon" />
|
|
|
|
+ <span class="add-text">添加应用</span>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 右键菜单 -->
|
|
|
|
+ <div
|
|
|
|
+ v-show="showContextMenu"
|
|
|
|
+ class="context-menu"
|
|
|
|
+ :style="{ left: contextMenuX + 'px', top: contextMenuY + 'px' }"
|
|
|
|
+ @click.stop
|
|
|
|
+ @contextmenu.prevent.stop
|
|
|
|
+ >
|
|
|
|
+ <div class="menu-item" @click.stop="handleEdit">
|
|
|
|
+ <i class="el-icon-edit" />
|
|
|
|
+ <span>编辑应用</span>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="menu-item" @click.stop="handleDelete">
|
|
|
|
+ <i class="el-icon-delete" />
|
|
|
|
+ <span>删除应用</span>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="menu-item" @click.stop="handleCopy">
|
|
|
|
+ <i class="el-icon-document-copy" />
|
|
|
|
+ <span>复制应用</span>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="menu-item" @click.stop="handleShare">
|
|
|
|
+ <i class="el-icon-share" />
|
|
|
|
+ <span>分享应用</span>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="menu-item" @click.stop="handleBindDataSource">
|
|
|
|
+ <i class="el-icon-connection" />
|
|
|
|
+ <span>绑定数据源</span>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 分页 -->
|
|
|
|
+ <div class="pagination-container">
|
|
|
|
+ <el-pagination
|
|
|
|
+ background
|
|
|
|
+ :current-page="queryParams.pageNum"
|
|
|
|
+ :page-sizes="[12, 24, 36, 48]"
|
|
|
|
+ :page-size="queryParams.pageSize"
|
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
|
+ :total="total"
|
|
|
|
+ @size-change="handleSizeChange"
|
|
|
|
+ @current-change="handleCurrentChange"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 添加应用弹窗 -->
|
|
|
|
+ <el-dialog
|
|
|
|
+ title="添加应用"
|
|
|
|
+ :visible.sync="showAddAppModal"
|
|
|
|
+ width="600px"
|
|
|
|
+ :close-on-click-modal="false"
|
|
|
|
+ @closed="resetForm"
|
|
|
|
+ >
|
|
|
|
+ <el-form
|
|
|
|
+ ref="addAppForm"
|
|
|
|
+ :model="addAppForm"
|
|
|
|
+ :rules="addAppRules"
|
|
|
|
+ label-width="100px"
|
|
|
|
+ size="small"
|
|
|
|
+ >
|
|
|
|
+ <el-form-item label="应用名称" prop="appName">
|
|
|
|
+ <el-input v-model="addAppForm.appName" placeholder="请输入应用名称" />
|
|
|
|
+ </el-form-item>
|
|
|
|
+
|
|
|
|
+ <el-form-item label="应用分类" prop="category">
|
|
|
|
+ <el-select v-model="addAppForm.category" placeholder="请选择应用分类" style="width: 100%">
|
|
|
|
+ <el-option
|
|
|
|
+ v-for="item in categories"
|
|
|
|
+ :key="item.id"
|
|
|
|
+ :label="item.categoryName"
|
|
|
|
+ :value="item.id"
|
|
|
|
+ />
|
|
|
|
+ </el-select>
|
|
|
|
+ </el-form-item>
|
|
|
|
+
|
|
|
|
+ <el-form-item label="应用图标" prop="icon">
|
|
|
|
+ <el-select v-model="addAppForm.iconUrl" placeholder="请选择应用图标" style="width: 100%" @change="handleIconChange">
|
|
|
|
+ <el-option
|
|
|
|
+ v-for="icon in iconOptions"
|
|
|
|
+ :key="icon.value"
|
|
|
|
+ :label="icon.label"
|
|
|
|
+ :value="icon.value"
|
|
|
|
+ >
|
|
|
|
+ <i :class="icon.value" style="margin-right: 8px;margin-left: 15px;" />
|
|
|
|
+ {{ icon.label }}
|
|
|
|
+ </el-option>
|
|
|
|
+ </el-select>
|
|
|
|
+ </el-form-item>
|
|
|
|
+
|
|
|
|
+ <el-form-item label="图标颜色" prop="iconColor">
|
|
|
|
+ <el-color-picker v-model="addAppForm.iconColor" show-alpha />
|
|
|
|
+ </el-form-item>
|
|
|
|
+
|
|
|
|
+ <el-form-item label="应用描述" prop="description">
|
|
|
|
+ <el-input
|
|
|
|
+ v-model="addAppForm.description"
|
|
|
|
+ type="textarea"
|
|
|
|
+ :rows="4"
|
|
|
|
+ placeholder="请输入应用描述"
|
|
|
|
+ />
|
|
|
|
+ </el-form-item>
|
|
|
|
+ </el-form>
|
|
|
|
+
|
|
|
|
+ <div slot="footer" class="dialog-footer">
|
|
|
|
+ <el-button @click="showAddAppModal = false">取 消</el-button>
|
|
|
|
+ <el-button type="primary" :loading="submitting" @click="handleAddApp">确 定</el-button>
|
|
|
|
+ </div>
|
|
|
|
+ </el-dialog>
|
|
|
|
+
|
|
|
|
+ <!-- 编辑应用弹窗 -->
|
|
|
|
+ <el-dialog
|
|
|
|
+ title="编辑应用"
|
|
|
|
+ :visible.sync="showEditDialog"
|
|
|
|
+ width="600px"
|
|
|
|
+ :close-on-click-modal="false"
|
|
|
|
+ @closed="resetEditForm"
|
|
|
|
+ >
|
|
|
|
+ <el-form
|
|
|
|
+ ref="editForm"
|
|
|
|
+ :model="editForm"
|
|
|
|
+ :rules="addAppRules"
|
|
|
|
+ label-width="100px"
|
|
|
|
+ size="small"
|
|
|
|
+ >
|
|
|
|
+ <el-form-item label="应用名称" prop="appName">
|
|
|
|
+ <el-input v-model="editForm.appName" placeholder="请输入应用名称" />
|
|
|
|
+ </el-form-item>
|
|
|
|
+
|
|
|
|
+ <el-form-item label="应用分类" prop="category">
|
|
|
|
+ <el-select v-model="editForm.category" placeholder="请选择应用分类" style="width: 100%">
|
|
|
|
+ <el-option
|
|
|
|
+ v-for="item in categories"
|
|
|
|
+ :key="item.id"
|
|
|
|
+ :label="item.categoryName"
|
|
|
|
+ :value="item.id"
|
|
|
|
+ />
|
|
|
|
+ </el-select>
|
|
|
|
+ </el-form-item>
|
|
|
|
+
|
|
|
|
+ <el-form-item label="应用图标" prop="iconUrl">
|
|
|
|
+ <el-select v-model="editForm.iconUrl" placeholder="请选择应用图标" style="width: 100%" @change="handleEditIconChange">
|
|
|
|
+ <el-option
|
|
|
|
+ v-for="icon in iconOptions"
|
|
|
|
+ :key="icon.value"
|
|
|
|
+ :label="icon.label"
|
|
|
|
+ :value="icon.value"
|
|
|
|
+ >
|
|
|
|
+ <i :class="icon.value" style="margin-right: 8px;margin-left: 15px;" />
|
|
|
|
+ {{ icon.label }}
|
|
|
|
+ </el-option>
|
|
|
|
+ </el-select>
|
|
|
|
+ </el-form-item>
|
|
|
|
+
|
|
|
|
+ <el-form-item label="图标颜色" prop="iconColor">
|
|
|
|
+ <el-color-picker v-model="editForm.iconColor" show-alpha />
|
|
|
|
+ </el-form-item>
|
|
|
|
+
|
|
|
|
+ <el-form-item label="应用描述" prop="description">
|
|
|
|
+ <el-input
|
|
|
|
+ v-model="editForm.description"
|
|
|
|
+ type="textarea"
|
|
|
|
+ :rows="4"
|
|
|
|
+ placeholder="请输入应用描述"
|
|
|
|
+ />
|
|
|
|
+ </el-form-item>
|
|
|
|
+ </el-form>
|
|
|
|
+
|
|
|
|
+ <div slot="footer" class="dialog-footer">
|
|
|
|
+ <el-button @click="showEditDialog = false">取 消</el-button>
|
|
|
|
+ <el-button type="primary" :loading="submitting" @click="handleEditSubmit">确 定</el-button>
|
|
|
|
+ </div>
|
|
|
|
+ </el-dialog>
|
|
|
|
+
|
|
|
|
+ <!-- 数据源绑定弹窗 -->
|
|
|
|
+ <el-dialog
|
|
|
|
+ title="绑定数据源"
|
|
|
|
+ :visible.sync="showBindDialog"
|
|
|
|
+ width="800px"
|
|
|
|
+ :close-on-click-modal="false"
|
|
|
|
+ @closed="resetBindForm"
|
|
|
|
+ >
|
|
|
|
+ <el-form
|
|
|
|
+ ref="bindForm"
|
|
|
|
+ :model="bindForm"
|
|
|
|
+ :rules="bindRules"
|
|
|
|
+ label-width="120px"
|
|
|
|
+ size="small"
|
|
|
|
+ >
|
|
|
|
+ <el-form-item label="数据源类型" prop="type">
|
|
|
|
+ <el-select v-model="bindForm.type" placeholder="请选择数据源类型" style="width: 100%">
|
|
|
|
+ <!--<el-option label="MySQL" value="mysql">-->
|
|
|
|
+ <!-- <i class="el-icon-coin" style="color: #4479a1;margin-left: 12px;" />-->
|
|
|
|
+ <!-- <span style="margin-left: -16px;">MySQL</span>-->
|
|
|
|
+ <!--</el-option>-->
|
|
|
|
+ <!--<el-option label="PostgreSQL" value="postgresql">-->
|
|
|
|
+ <!-- <i class="el-icon-coin" style="color: #336791;margin-left: 12px; " />-->
|
|
|
|
+ <!-- <span style="margin-left: -16px;;">PostgreSQL</span>-->
|
|
|
|
+ <!--</el-option>-->
|
|
|
|
+ <!--<el-option label="Oracle" value="oracle">-->
|
|
|
|
+ <!-- <i class="el-icon-coin" style="color: #f80102;margin-left: 12px;" />-->
|
|
|
|
+ <!-- <span style="margin-left: -16px;;">Oracle</span>-->
|
|
|
|
+ <!--</el-option>-->
|
|
|
|
+ <!--<el-option label="SQL Server" value="sqlserver">-->
|
|
|
|
+ <!-- <i class="el-icon-coin" style="color: #f35325;margin-left: 12px;" />-->
|
|
|
|
+ <!-- <span style="margin-left: -16px;">SQL Server</span>-->
|
|
|
|
+ <!--</el-option>-->
|
|
|
|
+ <!--<el-option label="Redis" value="redis">-->
|
|
|
|
+ <!-- <i class="el-icon-coin" style="color: #dc382d;margin-left: 12px;" />-->
|
|
|
|
+ <!-- <span style="margin-left: -16px;">Redis</span>-->
|
|
|
|
+ <!--</el-option>-->
|
|
|
|
+ </el-select>
|
|
|
|
+ </el-form-item>
|
|
|
|
+
|
|
|
|
+ <el-form-item label="连接名称" prop="name">
|
|
|
|
+ <el-input v-model="bindForm.name" placeholder="请输入连接名称" />
|
|
|
|
+ </el-form-item>
|
|
|
|
+
|
|
|
|
+ <el-form-item label="主机地址" prop="host">
|
|
|
|
+ <el-input v-model="bindForm.host" placeholder="请输入主机地址" />
|
|
|
|
+ </el-form-item>
|
|
|
|
+
|
|
|
|
+ <el-form-item label="端口" prop="port">
|
|
|
|
+ <el-input-number
|
|
|
|
+ v-model="bindForm.port"
|
|
|
|
+ :min="1"
|
|
|
|
+ :max="65535"
|
|
|
|
+ style="width: 100%"
|
|
|
|
+ />
|
|
|
|
+ </el-form-item>
|
|
|
|
+
|
|
|
|
+ <el-form-item v-if="bindForm.type !== 'redis'" label="数据库名" prop="database">
|
|
|
|
+ <el-input v-model="bindForm.database" placeholder="请输入数据库名" />
|
|
|
|
+ </el-form-item>
|
|
|
|
+
|
|
|
|
+ <el-form-item label="用户名" prop="username">
|
|
|
|
+ <el-input v-model="bindForm.username" placeholder="请输入用户名" />
|
|
|
|
+ </el-form-item>
|
|
|
|
+
|
|
|
|
+ <el-form-item label="密码" prop="password">
|
|
|
|
+ <el-input
|
|
|
|
+ v-model="bindForm.password"
|
|
|
|
+ type="password"
|
|
|
|
+ placeholder="请输入密码"
|
|
|
|
+ show-password
|
|
|
|
+ />
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <el-form-item label="应用管理员账号" prop="adminName">
|
|
|
|
+ <el-input v-model="bindForm.adminName" placeholder="应用管理员账号" />
|
|
|
|
+ </el-form-item>
|
|
|
|
+ </el-form>
|
|
|
|
+
|
|
|
|
+ <div slot="footer" class="dialog-footer">
|
|
|
|
+ <el-button @click="showBindDialog = false">取 消</el-button>
|
|
|
|
+ <el-button type="primary" :loading="testing" @click="handleTestConnection">测试连接</el-button>
|
|
|
|
+ <el-button type="success" :loading="saving" @click="handleSaveConnection">绑定数据源</el-button>
|
|
|
|
+ </div>
|
|
|
|
+ </el-dialog>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+</template>
|
|
|
|
+
|
|
|
|
+<script>
|
|
|
|
+import { listApplications, getApplications, addApplications, updateApplications, delApplications, allClassification, getDataSourceInformation,testConnection,applicationInitDataSource } from '@/api/application/applications'
|
|
|
|
+import { listCategories } from '@/api/application/categories'
|
|
|
|
+import { mapGetters } from 'vuex'
|
|
|
|
+
|
|
|
|
+export default {
|
|
|
|
+ name: 'AppPlatform',
|
|
|
|
+ data() {
|
|
|
|
+ // 端口号验证函数
|
|
|
|
+ const validatePort = (rule, value, callback) => {
|
|
|
|
+ if (value === '') {
|
|
|
|
+ callback(new Error('请输入端口号'))
|
|
|
|
+ } else if (value < 1 || value > 65535) {
|
|
|
|
+ callback(new Error('端口号必须在1-65535之间'))
|
|
|
|
+ } else {
|
|
|
|
+ callback()
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return {
|
|
|
|
+ searchQuery: '',
|
|
|
|
+ showUserMenu: false,
|
|
|
|
+ currentCategory: 'all',
|
|
|
|
+ showAddAppModal: false,
|
|
|
|
+ currentPage: 1,
|
|
|
|
+ pageSize: 10,
|
|
|
|
+ user: {
|
|
|
|
+ name: '张三',
|
|
|
|
+ avatar: '' // 可以设置头像URL
|
|
|
|
+ },
|
|
|
|
+ categories: [],
|
|
|
|
+ categoriesTwo: [],
|
|
|
|
+ apps: [],
|
|
|
|
+ submitting: false,
|
|
|
|
+ addAppForm: {
|
|
|
|
+ appName: '',
|
|
|
|
+ category: '',
|
|
|
|
+ iconUrl: '',
|
|
|
|
+ iconColor: '',
|
|
|
|
+ description: '',
|
|
|
|
+ icon: ''
|
|
|
|
+ },
|
|
|
|
+ addAppRules: {
|
|
|
|
+ appName: [
|
|
|
|
+ { required: true, message: '请输入应用名称', trigger: 'blur' },
|
|
|
|
+ { min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
|
|
|
|
+ ],
|
|
|
|
+ category: [
|
|
|
|
+ { required: true, message: '请选择应用分类', trigger: 'change' }
|
|
|
|
+ ],
|
|
|
|
+ iconUrl: [
|
|
|
|
+ { required: true, message: '请输入图标地址', trigger: 'blur' }
|
|
|
|
+ ],
|
|
|
|
+ iconColor: [
|
|
|
|
+ { required: true, message: '请选择图标颜色', trigger: 'change' }
|
|
|
|
+ ],
|
|
|
|
+ description: [
|
|
|
|
+ { required: true, message: '请输入应用描述', trigger: 'blur' },
|
|
|
|
+ { max: 200, message: '描述不能超过200个字符', trigger: 'blur' }
|
|
|
|
+ ]
|
|
|
|
+ },
|
|
|
|
+ iconOptions: [
|
|
|
|
+ { value: 'el-icon-s-platform', label: 'HIS系统' },
|
|
|
|
+ { value: 'el-icon-s-management', label: 'WMS系统' },
|
|
|
|
+ { value: 'el-icon-s-cooperation', label: 'CRM系统' },
|
|
|
|
+ { value: 'el-icon-s-marketing', label: 'HR系统' },
|
|
|
|
+ { value: 'el-icon-s-data', label: 'ERP系统' },
|
|
|
|
+ { value: 'el-icon-s-opportunity', label: 'OA系统' },
|
|
|
|
+ { value: 'el-icon-s-custom', label: 'MES系统' },
|
|
|
|
+ { value: 'el-icon-s-grid', label: 'SCM系统' },
|
|
|
|
+ { value: 'el-icon-s-order', label: 'SRM系统' },
|
|
|
|
+ { value: 'el-icon-s-finance', label: '财务系统' },
|
|
|
|
+ { value: 'el-icon-s-marketing', label: '营销系统' },
|
|
|
|
+ { value: 'el-icon-s-data', label: '数据分析' },
|
|
|
|
+ { value: 'el-icon-s-opportunity', label: '客户管理' },
|
|
|
|
+ { value: 'el-icon-s-custom', label: '库存管理' },
|
|
|
|
+ { value: 'el-icon-s-grid', label: '生产管理' }
|
|
|
|
+ ],
|
|
|
|
+ searchKeyword: '',
|
|
|
|
+ filteredApps: [],
|
|
|
|
+ showContextMenu: false,
|
|
|
|
+ contextMenuX: 0,
|
|
|
|
+ contextMenuY: 0,
|
|
|
|
+ selectedApp: null,
|
|
|
|
+ showBindDialog: false,
|
|
|
|
+ testing: false,
|
|
|
|
+ saving: false,
|
|
|
|
+ bindForm: {
|
|
|
|
+ type: '',
|
|
|
|
+ name: '',
|
|
|
|
+ host: '',
|
|
|
|
+ port: 3306,
|
|
|
|
+ database: '',
|
|
|
|
+ username: '',
|
|
|
|
+ password: '',
|
|
|
|
+ remark: '',
|
|
|
|
+ adminName: '',
|
|
|
|
+ clintId:""
|
|
|
|
+ },
|
|
|
|
+ bindRules: {
|
|
|
|
+ type: [
|
|
|
|
+ { required: true, message: '请选择数据源类型', trigger: 'change' }
|
|
|
|
+ ],
|
|
|
|
+ name: [
|
|
|
|
+ { required: true, message: '请输入连接名称', trigger: 'blur' },
|
|
|
|
+ { min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
|
|
|
|
+ ],
|
|
|
|
+ host: [
|
|
|
|
+ { required: true, message: '请输入主机地址', trigger: 'blur' }
|
|
|
|
+ ],
|
|
|
|
+ port: [
|
|
|
|
+ { required: true, message: '请输入端口号', trigger: 'blur' },
|
|
|
|
+ { validator: validatePort, trigger: 'blur' }
|
|
|
|
+ ],
|
|
|
|
+ database: [
|
|
|
|
+ { required: true, message: '请输入数据库名', trigger: 'blur' }
|
|
|
|
+ ],
|
|
|
|
+ username: [
|
|
|
|
+ { required: true, message: '请输入用户名', trigger: 'blur' }
|
|
|
|
+ ],
|
|
|
|
+ password: [
|
|
|
|
+ { required: true, message: '请输入密码', trigger: 'blur' }
|
|
|
|
+ ],
|
|
|
|
+ adminName: [
|
|
|
|
+ { required: true, message: '请输入应用管理员账号', trigger: 'blur' }
|
|
|
|
+ ]
|
|
|
|
+ },
|
|
|
|
+ showEditDialog: false,
|
|
|
|
+ editForm: {
|
|
|
|
+ id: null,
|
|
|
|
+ appName: '',
|
|
|
|
+ category: '',
|
|
|
|
+ iconUrl: '',
|
|
|
|
+ iconColor: '',
|
|
|
|
+ description: ''
|
|
|
|
+ },
|
|
|
|
+ // 查询参数
|
|
|
|
+ queryParams: {
|
|
|
|
+ pageNum: 1,
|
|
|
|
+ pageSize: 12,
|
|
|
|
+ keyword: ''
|
|
|
|
+ },
|
|
|
|
+ // 总条数
|
|
|
|
+ total: 0,
|
|
|
|
+ // 应用列表
|
|
|
|
+ appList: [],
|
|
|
|
+ loading: false,
|
|
|
|
+ dataSourceInformationQuery: {
|
|
|
|
+ pageNum: 1,
|
|
|
|
+ pageSize: 10,
|
|
|
|
+ dictType: 'mysql_connection_information'
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ computed: {
|
|
|
|
+ ...mapGetters([
|
|
|
|
+ 'avatar',
|
|
|
|
+ 'name'
|
|
|
|
+ ]),
|
|
|
|
+ // 根据当前分类过滤应用
|
|
|
|
+ currentApps() {
|
|
|
|
+ return this.filteredApps || []
|
|
|
|
+ },
|
|
|
|
+ // 分页后的应用列表
|
|
|
|
+ paginatedApps() {
|
|
|
|
+ const start = (this.currentPage - 1) * this.pageSize
|
|
|
|
+ const end = start + this.pageSize
|
|
|
|
+ return (this.currentApps || []).slice(start, end)
|
|
|
|
+ },
|
|
|
|
+ // 总条目数
|
|
|
|
+ totalItems() {
|
|
|
|
+ return (this.currentApps || []).length
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ watch: {
|
|
|
|
+ // 当分类改变时重新获取应用列表
|
|
|
|
+ currentCategory() {
|
|
|
|
+ this.currentPage = 1
|
|
|
|
+ this.getList()
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ created() {
|
|
|
|
+ // 获取分类信息
|
|
|
|
+ this.getClassificationList()
|
|
|
|
+ // 获取应用列表
|
|
|
|
+ this.getList()
|
|
|
|
+ // 获取新增时候的分类列表
|
|
|
|
+ this.getCategories()
|
|
|
|
+ // 获取数据源信息
|
|
|
|
+ this.getDataSourceInformation()
|
|
|
|
+ },
|
|
|
|
+ mounted() {
|
|
|
|
+ // 点击或右键其他区域关闭右键菜单
|
|
|
|
+ document.addEventListener('click', this.closeContextMenu)
|
|
|
|
+ document.addEventListener('contextmenu', this.closeContextMenu)
|
|
|
|
+ // 滚动时关闭右键菜单
|
|
|
|
+ document.addEventListener('scroll', this.closeContextMenu)
|
|
|
|
+ },
|
|
|
|
+ beforeDestroy() {
|
|
|
|
+ // 移除事件监听
|
|
|
|
+ document.removeEventListener('click', this.closeContextMenu)
|
|
|
|
+ document.removeEventListener('contextmenu', this.closeContextMenu)
|
|
|
|
+ document.removeEventListener('scroll', this.closeContextMenu)
|
|
|
|
+ },
|
|
|
|
+ methods: {
|
|
|
|
+ // ...其他方法...
|
|
|
|
+ handleAppDblClick(app) {
|
|
|
|
+ this.$confirm('是否要进入系统?', '提示', {
|
|
|
|
+ confirmButtonText: '进入',
|
|
|
|
+ cancelButtonText: '取消',
|
|
|
|
+ type: 'warning'
|
|
|
|
+ }).then(() => {
|
|
|
|
+ // 这里写进入系统的逻辑,比如跳转
|
|
|
|
+ this.$message.success('即将进入系统:' + app.appName)
|
|
|
|
+ // 例如:window.open(app.url) 或 this.$router.push(...)
|
|
|
|
+ }).catch(() => {
|
|
|
|
+ // 用户取消
|
|
|
|
+ })
|
|
|
|
+ },
|
|
|
|
+ handleLogout() {
|
|
|
|
+ this.$confirm('确定注销并退出系统吗?', '提示', {
|
|
|
|
+ confirmButtonText: '确定',
|
|
|
|
+ cancelButtonText: '取消',
|
|
|
|
+ type: 'warning'
|
|
|
|
+ }).then(() => {
|
|
|
|
+ this.$store.dispatch('LogOut').then(() => {
|
|
|
|
+ location.href = '/index'
|
|
|
|
+ })
|
|
|
|
+ }).catch(() => {})
|
|
|
|
+ },
|
|
|
|
+ async getDataSourceInformation() {
|
|
|
|
+ const response = await getDataSourceInformation(this.dataSourceInformationQuery)
|
|
|
|
+ if (response.code === 200) {
|
|
|
|
+ // 转换为对象
|
|
|
|
+ const info = {}
|
|
|
|
+ response.rows.forEach(item => {
|
|
|
|
+ info[item.dictLabel] = item.dictValue
|
|
|
|
+ })
|
|
|
|
+ // 填充表单
|
|
|
|
+ this.bindForm = {
|
|
|
|
+ ...this.bindForm,
|
|
|
|
+ database: info.databaseName || '',
|
|
|
|
+ name: info.databaseIp || '',
|
|
|
|
+ host: info.databaseIp || '',
|
|
|
|
+ username: info.username || '',
|
|
|
|
+ password: info.password || '',
|
|
|
|
+ port: Number(info.portNumber) || 3306,
|
|
|
|
+ type: info.databaseType || ''
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ // 获取分类列表
|
|
|
|
+ async getCategories() {
|
|
|
|
+ try {
|
|
|
|
+ const response = await listCategories()
|
|
|
|
+ if (response.code === 200) {
|
|
|
|
+ this.categories = response.rows
|
|
|
|
+ }
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('获取分类列表失败:', error)
|
|
|
|
+ this.$message.error('获取分类列表失败')
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ // 获取分类信息
|
|
|
|
+ getClassificationList() {
|
|
|
|
+ allClassification().then(response => {
|
|
|
|
+ if (response.code === 200) {
|
|
|
|
+ this.categoriesTwo = response.data
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ },
|
|
|
|
+ // 获取应用列表
|
|
|
|
+ getList() {
|
|
|
|
+ this.loading = true
|
|
|
|
+ const params = {
|
|
|
|
+ ...this.queryParams,
|
|
|
|
+ category: this.currentCategory === 'all' ? '' : this.currentCategory
|
|
|
|
+ }
|
|
|
|
+ listApplications(params).then(response => {
|
|
|
|
+ this.appList = response.rows
|
|
|
|
+ this.total = response.total
|
|
|
|
+ this.loading = false
|
|
|
|
+ // 获取分类信息
|
|
|
|
+ this.getClassificationList()
|
|
|
|
+ }).catch(() => {
|
|
|
|
+ this.loading = false
|
|
|
|
+ })
|
|
|
|
+ },
|
|
|
|
+ // 搜索
|
|
|
|
+ handleSearch() {
|
|
|
|
+ this.queryParams.keyword = this.searchKeyword
|
|
|
|
+ this.queryParams.pageNum = 1
|
|
|
|
+ this.getList()
|
|
|
|
+ },
|
|
|
|
+ // 分页大小改变
|
|
|
|
+ handleSizeChange(val) {
|
|
|
|
+ this.queryParams.pageSize = val
|
|
|
|
+ this.getList()
|
|
|
|
+ },
|
|
|
|
+ // 页码改变
|
|
|
|
+ handleCurrentChange(val) {
|
|
|
|
+ this.queryParams.pageNum = val
|
|
|
|
+ this.getList()
|
|
|
|
+ },
|
|
|
|
+ // 重置表单
|
|
|
|
+ resetForm() {
|
|
|
|
+ this.addAppForm = {
|
|
|
|
+ appName: '',
|
|
|
|
+ category: '',
|
|
|
|
+ iconUrl: '',
|
|
|
|
+ iconColor: '',
|
|
|
|
+ description: ''
|
|
|
|
+ }
|
|
|
|
+ if (this.$refs.addAppForm) {
|
|
|
|
+ this.$refs.addAppForm.resetFields()
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ handleAppClick(app) {
|
|
|
|
+ // 获取应用详情
|
|
|
|
+ getApplications(app.id).then(response => {
|
|
|
|
+ if (response.code === 200) {
|
|
|
|
+ console.log('应用详情:', response.data)
|
|
|
|
+ // 这里可以添加跳转到应用详情页的逻辑
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ },
|
|
|
|
+ handleAddApp() {
|
|
|
|
+ this.$refs.addAppForm.validate(valid => {
|
|
|
|
+ if (valid) {
|
|
|
|
+ this.submitting = true
|
|
|
|
+ addApplications(this.addAppForm).then(response => {
|
|
|
|
+ if (response.code === 200) {
|
|
|
|
+ this.$message.success('添加应用成功')
|
|
|
|
+ this.showAddAppModal = false
|
|
|
|
+ this.getList()
|
|
|
|
+ }
|
|
|
|
+ this.submitting = false
|
|
|
|
+ }).catch(() => {
|
|
|
|
+ this.submitting = false
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ },
|
|
|
|
+ // 处理右键菜单
|
|
|
|
+ handleContextMenu(event, app) {
|
|
|
|
+ // 阻止事件冒泡,防止触发document的右键事件
|
|
|
|
+ event.stopPropagation()
|
|
|
|
+ this.showContextMenu = true
|
|
|
|
+ this.contextMenuX = event.clientX
|
|
|
|
+ this.contextMenuY = event.clientY
|
|
|
|
+ this.selectedApp = app
|
|
|
|
+ },
|
|
|
|
+ // 关闭右键菜单
|
|
|
|
+ closeContextMenu() {
|
|
|
|
+ this.showContextMenu = false
|
|
|
|
+ },
|
|
|
|
+ // 处理编辑图标变化
|
|
|
|
+ handleEditIconChange(value) {
|
|
|
|
+ // 根据选择的图标设置默认颜色
|
|
|
|
+ const defaultColors = {
|
|
|
|
+ 'el-icon-s-platform': '#409EFF', // HIS系统 - 蓝色
|
|
|
|
+ 'el-icon-s-management': '#67C23A', // WMS系统 - 绿色
|
|
|
|
+ 'el-icon-s-cooperation': '#E6A23C', // CRM系统 - 橙色
|
|
|
|
+ 'el-icon-s-marketing': '#F56C6C', // HR系统 - 红色
|
|
|
|
+ 'el-icon-s-data': '#909399', // ERP系统 - 灰色
|
|
|
|
+ 'el-icon-s-opportunity': '#9B59B6', // OA系统 - 紫色
|
|
|
|
+ 'el-icon-s-custom': '#34495E', // MES系统 - 深蓝
|
|
|
|
+ 'el-icon-s-grid': '#16A085', // SCM系统 - 青色
|
|
|
|
+ 'el-icon-s-order': '#D35400', // SRM系统 - 棕色
|
|
|
|
+ 'el-icon-s-finance': '#27AE60' // 财务系统 - 深绿
|
|
|
|
+ }
|
|
|
|
+ this.editForm.iconColor = defaultColors[value] || '#409EFF'
|
|
|
|
+ },
|
|
|
|
+ // 处理编辑
|
|
|
|
+ handleEdit() {
|
|
|
|
+ console.log(this.selectedApp.category)
|
|
|
|
+ if (this.selectedApp) {
|
|
|
|
+ this.editForm = {
|
|
|
|
+ id: this.selectedApp.id,
|
|
|
|
+ appName: this.selectedApp.appName,
|
|
|
|
+ category: parseInt(this.selectedApp.category),
|
|
|
|
+ iconUrl: this.selectedApp.iconUrl,
|
|
|
|
+ iconColor: this.selectedApp.iconColor,
|
|
|
|
+ description: this.selectedApp.description
|
|
|
|
+ }
|
|
|
|
+ this.showEditDialog = true
|
|
|
|
+ }
|
|
|
|
+ this.showContextMenu = false
|
|
|
|
+ },
|
|
|
|
+ // 重置编辑表单
|
|
|
|
+ resetEditForm() {
|
|
|
|
+ if (this.$refs.editForm) {
|
|
|
|
+ this.$refs.editForm.resetFields()
|
|
|
|
+ }
|
|
|
|
+ this.editForm = {
|
|
|
|
+ id: null,
|
|
|
|
+ name: '',
|
|
|
|
+ category: '',
|
|
|
|
+ icon: '',
|
|
|
|
+ iconBg: '#1890ff',
|
|
|
|
+ description: ''
|
|
|
|
+ }
|
|
|
|
+ this.submitting = false
|
|
|
|
+ },
|
|
|
|
+ // 保存编辑
|
|
|
|
+ handleEditSubmit() {
|
|
|
|
+ this.$refs.editForm.validate((valid) => {
|
|
|
|
+ if (valid) {
|
|
|
|
+ this.submitting = true
|
|
|
|
+ updateApplications(this.editForm).then(response => {
|
|
|
|
+ if (response.code === 200) {
|
|
|
|
+ this.$message.success('保存成功')
|
|
|
|
+ this.showEditDialog = false
|
|
|
|
+ // 分类信息
|
|
|
|
+ this.getList()
|
|
|
|
+ }
|
|
|
|
+ this.submitting = false
|
|
|
|
+ }).catch(() => {
|
|
|
|
+ this.submitting = false
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ },
|
|
|
|
+ // 删除应用
|
|
|
|
+ handleDelete() {
|
|
|
|
+ this.$confirm('确认删除该应用吗?', '提示', {
|
|
|
|
+ confirmButtonText: '确定',
|
|
|
|
+ cancelButtonText: '取消',
|
|
|
|
+ type: 'warning'
|
|
|
|
+ }).then(() => {
|
|
|
|
+ delApplications(this.selectedApp.id).then(response => {
|
|
|
|
+ if (response.code === 200) {
|
|
|
|
+ this.$message.success('删除成功')
|
|
|
|
+ this.getList()
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ }).catch(() => {})
|
|
|
|
+ this.closeContextMenu()
|
|
|
|
+ },
|
|
|
|
+ // 复制应用
|
|
|
|
+ handleCopy() {
|
|
|
|
+ const newApp = {
|
|
|
|
+ ...this.selectedApp,
|
|
|
|
+ id: this.apps.length + 1,
|
|
|
|
+ name: this.selectedApp.name + ' (副本)',
|
|
|
|
+ date: new Date().toISOString().split('T')[0],
|
|
|
|
+ userCount: '0'
|
|
|
|
+ }
|
|
|
|
+ this.apps.unshift(newApp)
|
|
|
|
+ this.filteredApps = this.handleSearch()
|
|
|
|
+ this.$message.success('复制成功')
|
|
|
|
+ this.closeContextMenu()
|
|
|
|
+ },
|
|
|
|
+ // 处理绑定数据源
|
|
|
|
+ handleBindDataSource() {
|
|
|
|
+ // 获取数据源信息
|
|
|
|
+ this.getDataSourceInformation()
|
|
|
|
+ // 展示绑定数据源弹窗
|
|
|
|
+ this.showBindDialog = true
|
|
|
|
+ // 根据数据源类型设置默认端口
|
|
|
|
+ this.$watch('bindForm.type', (newType) => {
|
|
|
|
+ switch (newType) {
|
|
|
|
+ case 'mysql':
|
|
|
|
+ this.bindForm.port = 3306
|
|
|
|
+ break
|
|
|
|
+ case 'postgresql':
|
|
|
|
+ this.bindForm.port = 5432
|
|
|
|
+ break
|
|
|
|
+ case 'oracle':
|
|
|
|
+ this.bindForm.port = 1521
|
|
|
|
+ break
|
|
|
|
+ case 'sqlserver':
|
|
|
|
+ this.bindForm.port = 1433
|
|
|
|
+ break
|
|
|
|
+ case 'redis':
|
|
|
|
+ this.bindForm.port = 6379
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ this.bindForm.clientId=this.selectedApp.id;
|
|
|
|
+ console.log(this.bindForm);
|
|
|
|
+ this.closeContextMenu()
|
|
|
|
+ },
|
|
|
|
+ // 重置绑定表单
|
|
|
|
+ resetBindForm() {
|
|
|
|
+ if (this.$refs.bindForm) {
|
|
|
|
+ this.$refs.bindForm.resetFields()
|
|
|
|
+ }
|
|
|
|
+ this.bindForm = {
|
|
|
|
+ type: '',
|
|
|
|
+ name: '',
|
|
|
|
+ host: '',
|
|
|
|
+ port: 3306,
|
|
|
|
+ database: '',
|
|
|
|
+ username: '',
|
|
|
|
+ password: '',
|
|
|
|
+ remark: ''
|
|
|
|
+ }
|
|
|
|
+ this.testing = false
|
|
|
|
+ this.saving = false
|
|
|
|
+ },
|
|
|
|
+ // 测试连接
|
|
|
|
+ handleTestConnection() {
|
|
|
|
+ this.$refs.bindForm.validate((valid) => {
|
|
|
|
+ if (valid) {
|
|
|
|
+ this.testing = true
|
|
|
|
+ this.getTestConnection();
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ },
|
|
|
|
+ async getTestConnection() {
|
|
|
|
+ const resp = await testConnection(this.bindForm)
|
|
|
|
+ if (resp.code === 200) {
|
|
|
|
+ this.testing = false
|
|
|
|
+ this.$message.success('数据库连接测试成功')
|
|
|
|
+ } else {
|
|
|
|
+ this.testing = false;
|
|
|
|
+ this.$message.error('数据库连接测试失败')
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ async applicationInitDataSource() {
|
|
|
|
+ const resp = await applicationInitDataSource(this.bindForm)
|
|
|
|
+ if (resp.code === 200) {
|
|
|
|
+ this.testing = false
|
|
|
|
+ this.$message.success('初始化数据源成功')
|
|
|
|
+ } else {
|
|
|
|
+ this.testing = false
|
|
|
|
+ this.$message.error('初始化数据源失败')
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ // 保存连接
|
|
|
|
+ handleSaveConnection() {
|
|
|
|
+ // 前置校验数据库名称规则是否符合规范
|
|
|
|
+ this.$refs.bindForm.validate((valid) => {
|
|
|
|
+ if (valid) {
|
|
|
|
+ this.saving = true
|
|
|
|
+ const sourceData = {
|
|
|
|
+ ...this.bindForm,
|
|
|
|
+ applicationId: this.selectedApp.id
|
|
|
|
+ }
|
|
|
|
+ applicationInitDataSource(sourceData).then(response => {
|
|
|
|
+ if (response.code === 200) {
|
|
|
|
+ this.$message.success('数据源绑定成功')
|
|
|
|
+ this.showBindDialog = false
|
|
|
|
+ if (this.selectedApp) {
|
|
|
|
+ this.selectedApp.hasDataSource = true
|
|
|
|
+ }
|
|
|
|
+ this.getList()
|
|
|
|
+ }
|
|
|
|
+ this.saving = false
|
|
|
|
+ }).catch(() => {
|
|
|
|
+ this.saving = false
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ },
|
|
|
|
+ // 分享应用
|
|
|
|
+ handleShare() {
|
|
|
|
+ this.$message('分享功能开发中...')
|
|
|
|
+ this.closeContextMenu()
|
|
|
|
+ },
|
|
|
|
+ // 添加一个方法来处理添加卡片的点击
|
|
|
|
+ handleAddCardClick() {
|
|
|
|
+ console.log('点击添加卡片')
|
|
|
|
+ this.showAddAppModal = true
|
|
|
|
+ },
|
|
|
|
+ // 处理图标选择变化
|
|
|
|
+ handleIconChange(value) {
|
|
|
|
+ // 根据选择的图标设置默认颜色
|
|
|
|
+ const defaultColors = {
|
|
|
|
+ 'el-icon-s-platform': '#409EFF', // HIS系统 - 蓝色
|
|
|
|
+ 'el-icon-s-management': '#67C23A', // WMS系统 - 绿色
|
|
|
|
+ 'el-icon-s-cooperation': '#E6A23C', // CRM系统 - 橙色
|
|
|
|
+ 'el-icon-s-marketing': '#F56C6C', // HR系统 - 红色
|
|
|
|
+ 'el-icon-s-data': '#909399', // ERP系统 - 灰色
|
|
|
|
+ 'el-icon-s-opportunity': '#9B59B6', // OA系统 - 紫色
|
|
|
|
+ 'el-icon-s-custom': '#34495E', // MES系统 - 深蓝
|
|
|
|
+ 'el-icon-s-grid': '#16A085', // SCM系统 - 青色
|
|
|
|
+ 'el-icon-s-order': '#D35400', // SRM系统 - 棕色
|
|
|
|
+ 'el-icon-s-finance': '#27AE60' // 财务系统 - 深绿
|
|
|
|
+ }
|
|
|
|
+ this.addAppForm.iconColor = defaultColors[value] || '#409EFF'
|
|
|
|
+ },
|
|
|
|
+ handleAvatarError(e) {
|
|
|
|
+ // 图片加载失败时,设置为默认头像
|
|
|
|
+ e.target.src = require('@/assets/images/profile.jpg')
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+</script>
|
|
|
|
+
|
|
|
|
+<style lang="scss" scoped>
|
|
|
|
+.app-platform {
|
|
|
|
+ min-height: 100vh;
|
|
|
|
+ background: #f0f2f5;
|
|
|
|
+ padding: 20px 24px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.platform-header {
|
|
|
|
+ background: white;
|
|
|
|
+ padding: 16px 24px;
|
|
|
|
+ border-radius: 8px;
|
|
|
|
+ box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
|
|
|
+ margin-bottom: 24px;
|
|
|
|
+ display: flex;
|
|
|
|
+ justify-content: space-between;
|
|
|
|
+ align-items: center;
|
|
|
|
+
|
|
|
|
+ .header-left {
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ gap: 10px;
|
|
|
|
+
|
|
|
|
+ .header-logo {
|
|
|
|
+ width: 170px;
|
|
|
|
+ height: 50px;
|
|
|
|
+ object-fit: contain;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ h1 {
|
|
|
|
+ margin: 0;
|
|
|
|
+ font-size: 20px;
|
|
|
|
+ color: #333;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.platform-content {
|
|
|
|
+ background: white;
|
|
|
|
+ border-radius: 8px;
|
|
|
|
+ box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
|
|
|
+ padding: 24px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.platform-toolbar {
|
|
|
|
+ padding: 0 24px;
|
|
|
|
+ background: #fff;
|
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.toolbar-main {
|
|
|
|
+ display: flex;
|
|
|
|
+ justify-content: space-between;
|
|
|
|
+ align-items: center;
|
|
|
|
+ height: 56px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 分类按钮组样式 */
|
|
|
|
+.app-categories {
|
|
|
|
+ display: flex;
|
|
|
|
+ height: 100%;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.category-btn {
|
|
|
|
+ height: 100%;
|
|
|
|
+ padding: 0 20px;
|
|
|
|
+ border: none;
|
|
|
|
+ background: none;
|
|
|
|
+ color: #606266;
|
|
|
|
+ cursor: pointer;
|
|
|
|
+ transition: all 0.3s;
|
|
|
|
+ font-size: 14px;
|
|
|
|
+ position: relative;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.category-btn:hover {
|
|
|
|
+ color: #409EFF;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.category-btn.active {
|
|
|
|
+ color: #409EFF;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.category-btn.active::after {
|
|
|
|
+ content: '';
|
|
|
|
+ position: absolute;
|
|
|
|
+ bottom: 0;
|
|
|
|
+ left: 20%;
|
|
|
|
+ width: 60%;
|
|
|
|
+ height: 2px;
|
|
|
|
+ background: #409EFF;
|
|
|
|
+ border-radius: 1px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 搜索框样式调整 */
|
|
|
|
+.search-box {
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.search-box .el-input {
|
|
|
|
+ width: 320px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.search-box >>> .el-input__inner {
|
|
|
|
+ height: 32px;
|
|
|
|
+ line-height: 32px;
|
|
|
|
+ border-radius: 16px;
|
|
|
|
+ background: #f5f7fa;
|
|
|
|
+ border: 1px solid #f5f7fa;
|
|
|
|
+ padding-right: 35px;
|
|
|
|
+ transition: all 0.3s;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.search-box >>> .el-input__inner:hover {
|
|
|
|
+ border-color: #ebeef5;
|
|
|
|
+ background: #ebeef5;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.search-box >>> .el-input__inner:focus {
|
|
|
|
+ background: #fff;
|
|
|
|
+ border-color: #dcdfe6;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.search-box >>> .el-input__suffix {
|
|
|
|
+ right: 12px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.search-box >>> .el-input__suffix-inner {
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.search-box >>> .el-input__icon {
|
|
|
|
+ line-height: 32px;
|
|
|
|
+ color: #909399;
|
|
|
|
+ font-size: 16px;
|
|
|
|
+ cursor: pointer;
|
|
|
|
+ transition: all 0.3s;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.search-box >>> .el-input__icon:hover {
|
|
|
|
+ color: #409EFF;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.search-box >>> .el-input__clear {
|
|
|
|
+ right: 30px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.app-grid {
|
|
|
|
+ display: grid;
|
|
|
|
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
|
|
+ gap: 24px;
|
|
|
|
+ padding: 24px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.app-card {
|
|
|
|
+ height: auto;
|
|
|
|
+ min-height: 180px;
|
|
|
|
+ padding: 24px;
|
|
|
|
+ background: #fff;
|
|
|
|
+ border-radius: 8px;
|
|
|
|
+ cursor: pointer;
|
|
|
|
+ transition: all 0.3s;
|
|
|
|
+ position: relative;
|
|
|
|
+ border: 1px solid #e8e8e8;
|
|
|
|
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
|
|
+ display: flex;
|
|
|
|
+ flex-direction: column;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.app-card:hover {
|
|
|
|
+ transform: translateY(-2px);
|
|
|
|
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
|
|
+ border-color: #d9d9d9;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.app-icon {
|
|
|
|
+ width: 48px;
|
|
|
|
+ height: 48px;
|
|
|
|
+ border-radius: 12px;
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ justify-content: center;
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
+ font-size: 24px;
|
|
|
|
+ color: #fff;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.app-title {
|
|
|
|
+ font-size: 16px;
|
|
|
|
+ font-weight: 500;
|
|
|
|
+ color: #262626;
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
+ line-height: 1.4;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.app-desc {
|
|
|
|
+ font-size: 13px;
|
|
|
|
+ color: #8c8c8c;
|
|
|
|
+ line-height: 1.6;
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
+ flex: 1;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.app-footer {
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ font-size: 12px;
|
|
|
|
+ color: #bfbfbf;
|
|
|
|
+ margin-top: auto;
|
|
|
|
+ padding-top: 16px;
|
|
|
|
+ border-top: 1px solid #f0f0f0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.app-date {
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.app-date i {
|
|
|
|
+ margin-right: 6px;
|
|
|
|
+ font-size: 14px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 添加响应式布局 */
|
|
|
|
+@media screen and (max-width: 1400px) {
|
|
|
|
+ .app-grid {
|
|
|
|
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+@media screen and (max-width: 768px) {
|
|
|
|
+ .app-grid {
|
|
|
|
+ grid-template-columns: 1fr;
|
|
|
|
+ padding: 16px;
|
|
|
|
+ gap: 16px;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .app-card {
|
|
|
|
+ padding: 20px;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .app-icon {
|
|
|
|
+ width: 48px;
|
|
|
|
+ height: 48px;
|
|
|
|
+ border-radius: 12px;
|
|
|
|
+ margin-right: 16px;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .app-icon i {
|
|
|
|
+ font-size: 24px;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .app-name {
|
|
|
|
+ font-size: 16px;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .app-description {
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
+ font-size: 13px;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .app-stats {
|
|
|
|
+ gap: 12px;
|
|
|
|
+ font-size: 12px;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .stat {
|
|
|
|
+ padding: 3px 6px;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.user-actions {
|
|
|
|
+ position: relative;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.user-info {
|
|
|
|
+ cursor: pointer;
|
|
|
|
+ padding: 0 20px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.user-avatar-wrapper {
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.user-avatar {
|
|
|
|
+ width: 40px;
|
|
|
|
+ height: 40px;
|
|
|
|
+ border-radius: 50%;
|
|
|
|
+ margin-right: 10px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.user-avatar-placeholder {
|
|
|
|
+ width: 40px;
|
|
|
|
+ height: 40px;
|
|
|
|
+ border-radius: 50%;
|
|
|
|
+ background-color: #409EFF;
|
|
|
|
+ color: #fff;
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ justify-content: center;
|
|
|
|
+ font-size: 18px;
|
|
|
|
+ margin-right: 10px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.user-name {
|
|
|
|
+ font-size: 14px;
|
|
|
|
+ color: #606266;
|
|
|
|
+ margin-right: 5px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.el-icon-arrow-down {
|
|
|
|
+ font-size: 12px;
|
|
|
|
+ color: #909399;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.menu-item {
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ gap: 8px;
|
|
|
|
+ padding: 10px 16px;
|
|
|
|
+ cursor: pointer;
|
|
|
|
+ transition: all 0.3s;
|
|
|
|
+ white-space: nowrap;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.menu-item:hover {
|
|
|
|
+ background: #f5f5f5;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.menu-item i {
|
|
|
|
+ margin-right: 8px;
|
|
|
|
+ font-size: 16px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.pagination-container {
|
|
|
|
+ padding: 24px;
|
|
|
|
+ text-align: right;
|
|
|
|
+ background: white;
|
|
|
|
+ border-top: 1px solid #f0f0f0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+@media screen and (max-width: 768px) {
|
|
|
|
+ .pagination-container {
|
|
|
|
+ padding: 16px;
|
|
|
|
+ text-align: center;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.dialog-footer {
|
|
|
|
+ text-align: right;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.el-color-picker {
|
|
|
|
+ width: 100%;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 右键菜单样式 */
|
|
|
|
+.context-menu {
|
|
|
|
+ position: fixed;
|
|
|
|
+ background: white;
|
|
|
|
+ border-radius: 4px;
|
|
|
|
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
|
|
|
+ padding: 8px 0;
|
|
|
|
+ z-index: 3000;
|
|
|
|
+ min-width: 120px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.context-menu .menu-item {
|
|
|
|
+ padding: 8px 16px;
|
|
|
|
+ cursor: pointer;
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ transition: background-color 0.3s;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.context-menu .menu-item:hover {
|
|
|
|
+ background-color: #f5f7fa;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.context-menu .menu-item i {
|
|
|
|
+ margin-right: 8px;
|
|
|
|
+ font-size: 16px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.context-menu .menu-item span {
|
|
|
|
+ font-size: 14px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 数据源图标样式 */
|
|
|
|
+.el-select-dropdown__item i {
|
|
|
|
+ margin-right: 8px;
|
|
|
|
+ font-size: 16px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 调整表单中的数字输入框样式 */
|
|
|
|
+.el-input-number {
|
|
|
|
+ width: 100%;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 调整表单项间距 */
|
|
|
|
+.el-form-item {
|
|
|
|
+ margin-bottom: 18px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 添加应用卡片样式 */
|
|
|
|
+.add-card {
|
|
|
|
+ height: auto;
|
|
|
|
+ min-height: 180px;
|
|
|
|
+ padding: 24px;
|
|
|
|
+ background: #fff;
|
|
|
|
+ border-radius: 8px;
|
|
|
|
+ cursor: pointer;
|
|
|
|
+ transition: all 0.3s;
|
|
|
|
+ display: flex;
|
|
|
|
+ flex-direction: column;
|
|
|
|
+ align-items: center;
|
|
|
|
+ justify-content: center;
|
|
|
|
+ border: 2px dashed #e8e8e8;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.add-card:hover {
|
|
|
|
+ border-color: #409EFF;
|
|
|
|
+ background: #f5f7fa;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.add-icon {
|
|
|
|
+ font-size: 40px;
|
|
|
|
+ color: #bfbfbf;
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
+ transition: all 0.3s;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.add-card:hover .add-icon {
|
|
|
|
+ color: #409EFF;
|
|
|
|
+ transform: scale(1.1);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.add-text {
|
|
|
|
+ font-size: 15px;
|
|
|
|
+ color: #8c8c8c;
|
|
|
|
+ transition: all 0.3s;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.add-card:hover .add-text {
|
|
|
|
+ color: #409EFF;
|
|
|
|
+}
|
|
|
|
+.app-platform {
|
|
|
|
+ display: flex;
|
|
|
|
+ flex-direction: column;
|
|
|
|
+ min-height: 100vh;
|
|
|
|
+ background: #f0f2f5;
|
|
|
|
+ padding: 20px 24px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.platform-content {
|
|
|
|
+ background: white;
|
|
|
|
+ border-radius: 8px;
|
|
|
|
+ box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
|
|
|
+ padding: 24px;
|
|
|
|
+ flex: 1 1 0;
|
|
|
|
+ min-height: 0;
|
|
|
|
+ display: flex;
|
|
|
|
+ flex-direction: column;
|
|
|
|
+}
|
|
|
|
+.pagination-container {
|
|
|
|
+ margin-top: auto;
|
|
|
|
+ align-self: flex-end;
|
|
|
|
+ text-align: right;
|
|
|
|
+ background: white;
|
|
|
|
+ border-top: 1px solid #f0f0f0;
|
|
|
|
+ padding: 24px 0 0 0; /* 顶部留间距,底部不留白 */
|
|
|
|
+}
|
|
|
|
+</style>
|