feat: add MDI tabbed workspace
| | |
| | | </el-menu> |
| | | </el-aside> |
| | | <el-main class="layout-main"> |
| | | <transition name="fade" mode="out-in"> |
| | | <router-view></router-view> |
| | | </transition> |
| | | <div class="mdi-tabbar"> |
| | | <div class="mdi-caption"><i class="el-icon-document-copy"></i><span>工作区</span></div> |
| | | <div ref="tabScroll" class="mdi-tabs-scroll"> |
| | | <div class="mdi-tabs"> |
| | | <div |
| | | v-for="doc in openDocs" |
| | | :key="doc.fullPath" |
| | | class="mdi-tab" |
| | | :class="{ 'is-active': isActiveDoc(doc) }" |
| | | :title="doc.title" |
| | | @click="activateDoc(doc)" |
| | | > |
| | | <i :class="doc.icon"></i> |
| | | <span class="mdi-tab-title">{{ doc.title }}</span> |
| | | <i v-if="doc.pinned" class="el-icon-lock mdi-tab-pin"></i> |
| | | <i v-else class="el-icon-close mdi-tab-close" title="关闭" @click.stop="closeDoc(doc)"></i> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="mdi-actions"> |
| | | <el-dropdown trigger="click" @command="handleCommand"> |
| | | <button class="mdi-actions-button" type="button" title="文档操作"><i class="el-icon-more"></i></button> |
| | | <el-dropdown-menu slot="dropdown"> |
| | | <el-dropdown-item command="closeCurrent" :disabled="!activeDoc || activeDoc.pinned">关闭当前</el-dropdown-item> |
| | | <el-dropdown-item command="closeOthers" :disabled="!canCloseOthers">关闭其他</el-dropdown-item> |
| | | <el-dropdown-item command="closeAll" :disabled="openDocs.length <= 1">关闭全部</el-dropdown-item> |
| | | </el-dropdown-menu> |
| | | </el-dropdown> |
| | | </div> |
| | | </div> |
| | | <div ref="workspace" class="mdi-workspace" @scroll="rememberScroll"> |
| | | <router-view v-slot="{ Component, route }"> |
| | | <keep-alive :include="cachedNames" :max="20"> |
| | | <component :is="Component" :key="viewKey(route)" /> |
| | | </keep-alive> |
| | | </router-view> |
| | | </div> |
| | | </el-main> |
| | | </el-container> |
| | | </el-container> |
| | |
| | | import AppHeader from '@/components/AppHeader.vue' |
| | | import MenuNode from '@/components/MenuNode.vue' |
| | | |
| | | const HOME_DOC = { |
| | | fullPath: '/dashboard', |
| | | title: '首页门户', |
| | | icon: 'el-icon-s-home', |
| | | cacheName: 'Dashboard', |
| | | pinned: true, |
| | | version: 1 |
| | | } |
| | | |
| | | const REPORT_TITLES = { |
| | | overview: '装备故障总体分析', |
| | | 'top-equipment': '故障高发设备Top', |
| | | 'by-team': '按T统计', |
| | | contractors: '厂家维修情况', |
| | | trend: '年度趋势' |
| | | } |
| | | |
| | | export default { |
| | | name: 'AppLayout', |
| | | components: { AppHeader, MenuNode }, |
| | | data() { |
| | | return { menus: [] } |
| | | return { |
| | | menus: [], |
| | | openDocs: [], |
| | | currentFullPath: '', |
| | | cacheVersions: {}, |
| | | scrollTops: {} |
| | | } |
| | | }, |
| | | computed: { |
| | | activeDoc() { |
| | | return this.openDocs.find(doc => doc.fullPath === this.currentFullPath) || null |
| | | }, |
| | | cachedNames() { |
| | | return [...new Set(this.openDocs.map(doc => doc.cacheName).filter(Boolean))] |
| | | }, |
| | | canCloseOthers() { |
| | | const keepCount = this.activeDoc && this.activeDoc.pinned ? 1 : 2 |
| | | return this.openDocs.length > keepCount |
| | | } |
| | | }, |
| | | watch: { |
| | | '$route': { |
| | | immediate: true, |
| | | handler(route) { this.syncRoute(route) } |
| | | } |
| | | }, |
| | | created() { |
| | | this.loadMenus() |
| | |
| | | api.get('/system/menu/nav').then(res => { |
| | | this.menus = (res.data || []).filter(item => item.visible !== 0) |
| | | }).catch(() => {}) |
| | | }, |
| | | syncRoute(route) { |
| | | if (!route || route.path === '/login') return |
| | | if (this.currentFullPath && this.$refs.workspace) { |
| | | this.scrollTops[this.currentFullPath] = this.$refs.workspace.scrollTop |
| | | } |
| | | this.ensureHomeDoc() |
| | | const fullPath = route.fullPath |
| | | let doc = this.openDocs.find(item => item.fullPath === fullPath) |
| | | if (!doc) { |
| | | doc = { |
| | | fullPath, |
| | | title: this.resolveTitle(route), |
| | | icon: this.resolveIcon(route.path), |
| | | cacheName: route.meta.cacheName || route.path, |
| | | pinned: false, |
| | | version: (this.cacheVersions[fullPath] || 0) + 1 |
| | | } |
| | | this.openDocs.push(doc) |
| | | } else { |
| | | doc.title = this.resolveTitle(route) |
| | | doc.icon = this.resolveIcon(route.path) |
| | | doc.cacheName = route.meta.cacheName || doc.cacheName |
| | | } |
| | | this.currentFullPath = fullPath |
| | | this.$nextTick(() => { |
| | | this.restoreScroll() |
| | | this.scrollActiveTab() |
| | | }) |
| | | }, |
| | | ensureHomeDoc() { |
| | | if (!this.openDocs.some(doc => doc.pinned)) { |
| | | this.openDocs.unshift({ ...HOME_DOC }) |
| | | } |
| | | }, |
| | | resolveTitle(route) { |
| | | if (route.path.startsWith('/reports/')) { |
| | | return REPORT_TITLES[route.params.type] || route.meta.title || '统计报表' |
| | | } |
| | | return route.meta.title || route.name || route.path |
| | | }, |
| | | resolveIcon(path) { |
| | | if (path === '/dashboard') return 'el-icon-s-home' |
| | | if (path === '/fault/report') return 'el-icon-edit-outline' |
| | | if (path.includes('/approval/') || path === '/fault/review') return 'el-icon-s-check' |
| | | if (path === '/fault/dispatch') return 'el-icon-s-promotion' |
| | | if (path.startsWith('/fault/completion') || path === '/completion/report') return 'el-icon-finished' |
| | | if (path.startsWith('/acceptance/')) return 'el-icon-circle-check' |
| | | if (path.startsWith('/reports/')) return 'el-icon-data-analysis' |
| | | if (path.startsWith('/system/')) return 'el-icon-setting' |
| | | return 'el-icon-document' |
| | | }, |
| | | isActiveDoc(doc) { return doc.fullPath === this.currentFullPath }, |
| | | viewKey(route) { |
| | | const doc = this.openDocs.find(item => item.fullPath === route.fullPath) |
| | | return route.fullPath + '#' + (doc ? doc.version : 0) |
| | | }, |
| | | activateDoc(doc) { |
| | | if (!this.isActiveDoc(doc)) this.$router.push(doc.fullPath).catch(() => {}) |
| | | }, |
| | | closeDoc(doc) { |
| | | if (!doc || doc.pinned) return |
| | | const index = this.openDocs.findIndex(item => item.fullPath === doc.fullPath) |
| | | if (index < 0) return |
| | | const wasActive = this.isActiveDoc(doc) |
| | | this.cacheVersions[doc.fullPath] = doc.version + 1 |
| | | this.openDocs.splice(index, 1) |
| | | if (wasActive) { |
| | | const target = this.openDocs[index - 1] || this.openDocs[index] || this.openDocs[0] |
| | | this.$router.push(target.fullPath).catch(() => {}) |
| | | } else { |
| | | this.$nextTick(this.scrollActiveTab) |
| | | } |
| | | }, |
| | | handleCommand(command) { |
| | | if (command === 'closeCurrent') this.closeDoc(this.activeDoc) |
| | | if (command === 'closeOthers') this.closeOtherDocs() |
| | | if (command === 'closeAll') this.closeAllDocs() |
| | | }, |
| | | closeOtherDocs() { |
| | | if (!this.activeDoc) return |
| | | const keepPath = this.activeDoc.fullPath |
| | | this.openDocs.filter(doc => !doc.pinned && doc.fullPath !== keepPath).forEach(doc => { |
| | | this.cacheVersions[doc.fullPath] = doc.version + 1 |
| | | }) |
| | | this.openDocs = this.openDocs.filter(doc => doc.pinned || doc.fullPath === keepPath) |
| | | this.$nextTick(this.scrollActiveTab) |
| | | }, |
| | | closeAllDocs() { |
| | | this.openDocs.filter(doc => !doc.pinned).forEach(doc => { |
| | | this.cacheVersions[doc.fullPath] = doc.version + 1 |
| | | }) |
| | | this.openDocs = this.openDocs.filter(doc => doc.pinned) |
| | | this.$router.push('/dashboard').catch(() => {}) |
| | | }, |
| | | rememberScroll() { |
| | | if (this.currentFullPath && this.$refs.workspace) { |
| | | this.scrollTops[this.currentFullPath] = this.$refs.workspace.scrollTop |
| | | } |
| | | }, |
| | | restoreScroll() { |
| | | if (this.$refs.workspace) this.$refs.workspace.scrollTop = this.scrollTops[this.currentFullPath] || 0 |
| | | }, |
| | | scrollActiveTab() { |
| | | const active = this.$refs.tabScroll && this.$refs.tabScroll.querySelector('.mdi-tab.is-active') |
| | | if (active) active.scrollIntoView({ block: 'nearest', inline: 'nearest' }) |
| | | } |
| | | } |
| | | } |
| | |
| | | .layout-body { flex: 1; min-height: 0; } |
| | | .layout-aside { border-right: 1px solid #d9e2eb; background: #f5f8fb; overflow-x: hidden; overflow-y: auto; } |
| | | .aside-caption { padding: 14px 18px 8px; color: #8090a2; font-size: 12px; letter-spacing: .08em; } |
| | | .layout-main { min-width: 0; padding: 0; overflow: auto; background: #eef2f6; } |
| | | .layout-main { display: flex; flex-direction: column; min-width: 0; min-height: 0; padding: 0; overflow: hidden; background: #eef2f6; } |
| | | .el-menu { border-right: 0; } |
| | | .layout-aside ::v-deep .el-menu-item, |
| | | .layout-aside ::v-deep .el-submenu__title { |
| | |
| | | overflow-wrap: anywhere; |
| | | word-break: break-word; |
| | | } |
| | | .fade-enter-active, .fade-leave-active { transition: opacity .12s ease; } |
| | | .fade-enter, .fade-leave-to { opacity: 0; } |
| | | .mdi-tabbar { display: flex; flex: 0 0 40px; align-items: stretch; height: 40px; padding-top: 5px; border-bottom: 1px solid #cfdae5; background: #e3eaf1; } |
| | | .mdi-caption { display: flex; flex: 0 0 auto; align-items: center; gap: 6px; padding: 0 12px; border-right: 1px solid rgba(125, 147, 169, .22); color: #64788d; font-size: 12px; } |
| | | .mdi-caption i { color: #2c7fb8; font-size: 15px; } |
| | | .mdi-tabs-scroll { flex: 1; min-width: 0; overflow-x: auto; overflow-y: hidden; } |
| | | .mdi-tabs { display: inline-flex; align-items: flex-end; min-width: 100%; height: 100%; padding: 0 4px; } |
| | | .mdi-tab { display: inline-flex; flex: 0 0 auto; align-items: center; gap: 6px; max-width: 220px; height: 34px; margin-right: 2px; padding: 0 9px; border: 1px solid transparent; border-bottom: 0; border-radius: 4px 4px 0 0; color: #5c6f82; background: rgba(255, 255, 255, .42); cursor: pointer; font-size: 13px; user-select: none; } |
| | | .mdi-tab:hover { color: #245275; background: rgba(255, 255, 255, .72); } |
| | | .mdi-tab.is-active { height: 35px; color: #1f3348; background: #eef2f6; border-color: #cfdae5; box-shadow: inset 0 2px 0 #2c7fb8; } |
| | | .mdi-tab > i { flex: 0 0 auto; } |
| | | .mdi-tab-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } |
| | | .mdi-tab-close, .mdi-tab-pin { width: 16px; color: #8191a2; text-align: center; } |
| | | .mdi-tab-close { border-radius: 50%; font-size: 12px; } |
| | | .mdi-tab-close:hover { color: #fff; background: #d9534f; } |
| | | .mdi-tab-pin { font-size: 12px; } |
| | | .mdi-actions { display: flex; flex: 0 0 auto; align-items: center; padding: 0 6px; border-left: 1px solid rgba(125, 147, 169, .22); } |
| | | .mdi-actions-button { width: 30px; height: 28px; padding: 0; border: 0; border-radius: 3px; color: #587087; background: transparent; cursor: pointer; font-size: 17px; } |
| | | .mdi-actions-button:hover { color: #1f5f91; background: rgba(255, 255, 255, .68); } |
| | | .mdi-workspace { position: relative; flex: 1; min-height: 0; overflow: auto; background: #eef2f6; } |
| | | </style> |
| | |
| | | path: '/', |
| | | component: AppLayout, |
| | | children: [ |
| | | { path: 'dashboard', component: () => import('@/views/Dashboard.vue') }, |
| | | { path: 'fault/report', component: () => import('@/views/fault/FaultReport.vue') }, |
| | | { path: 'fault/review', component: () => import('@/views/fault/XcbReview.vue') }, |
| | | { path: 'fault/approval/cmb', component: () => import('@/views/fault/SimpleApproval.vue'), meta: { stage: 'CMB' } }, |
| | | { path: 'fault/approval/jcc-assistant', component: () => import('@/views/fault/SimpleApproval.vue'), meta: { stage: 'JCC_ASSISTANT' } }, |
| | | { path: 'fault/approval/jcc-director', component: () => import('@/views/fault/SimpleApproval.vue'), meta: { stage: 'JCC_DIRECTOR' } }, |
| | | { path: 'fault/dispatch', component: () => import('@/views/fault/DispatchPage.vue') }, |
| | | { path: 'fault/completion', component: () => import('@/views/completion/CompletionList.vue') }, |
| | | { path: 'completion/report', component: () => import('@/views/completion/CompletionReport.vue') }, |
| | | { path: 'acceptance/application', component: () => import('@/views/acceptance/AcceptanceList.vue'), meta: { stage: 'APPLICATION' } }, |
| | | { path: 'acceptance/inspector', component: () => import('@/views/acceptance/AcceptanceList.vue'), meta: { stage: 'INSPECTOR' } }, |
| | | { path: 'acceptance/leader', component: () => import('@/views/acceptance/AcceptanceList.vue'), meta: { stage: 'LEADER' } }, |
| | | { path: 'acceptance/print', component: () => import('@/views/acceptance/AcceptanceList.vue'), meta: { stage: 'PRINT' } }, |
| | | { path: 'acceptance/rating', component: () => import('@/views/acceptance/AcceptanceList.vue'), meta: { stage: 'RATING' } }, |
| | | { path: 'reports/:type', component: () => import('@/views/report/ReportPage.vue') }, |
| | | { path: 'system/contractors', component: () => import('@/views/system/ContractorManage.vue') }, |
| | | { path: 'system/product-tree', component: () => import('@/views/system/TreeManage.vue'), meta: { treeType: 'product' } }, |
| | | { path: 'system/base-equipment', component: () => import('@/views/system/TreeManage.vue'), meta: { treeType: 'base' } }, |
| | | { path: 'system/engineering-majors', component: () => import('@/views/system/EngineeringMajorManage.vue') }, |
| | | { path: 'system/team-majors', component: () => import('@/views/system/TeamMajorManage.vue') }, |
| | | { path: 'system/roles', component: () => import('@/views/system/RoleManage.vue') }, |
| | | { path: 'system/menus', component: () => import('@/views/system/MenuManage.vue') }, |
| | | { path: 'system/menu-permissions', component: () => import('@/views/system/MenuPermission.vue') }, |
| | | { path: 'system/three-member', component: () => import('@/views/system/ThreeMember.vue') } |
| | | { path: 'dashboard', component: () => import('@/views/Dashboard.vue'), meta: { title: '首页门户', cacheName: 'Dashboard' } }, |
| | | { path: 'fault/report', component: () => import('@/views/fault/FaultReport.vue'), meta: { title: '故障上报', cacheName: 'FaultReport' } }, |
| | | { path: 'fault/review', component: () => import('@/views/fault/XcbReview.vue'), meta: { title: 'XCB助理审核', cacheName: 'XcbReview' } }, |
| | | { path: 'fault/approval/cmb', component: () => import('@/views/fault/SimpleApproval.vue'), props: { stage: 'CMB' }, meta: { title: 'CMB领导审批', stage: 'CMB', cacheName: 'SimpleApproval' } }, |
| | | { path: 'fault/approval/jcc-assistant', component: () => import('@/views/fault/SimpleApproval.vue'), props: { stage: 'JCC_ASSISTANT' }, meta: { title: 'JCC助理审批', stage: 'JCC_ASSISTANT', cacheName: 'SimpleApproval' } }, |
| | | { path: 'fault/approval/jcc-director', component: () => import('@/views/fault/SimpleApproval.vue'), props: { stage: 'JCC_DIRECTOR' }, meta: { title: 'JCC处长审批', stage: 'JCC_DIRECTOR', cacheName: 'SimpleApproval' } }, |
| | | { path: 'fault/dispatch', component: () => import('@/views/fault/DispatchPage.vue'), meta: { title: 'XCB助理派单', cacheName: 'DispatchPage' } }, |
| | | { path: 'fault/completion', component: () => import('@/views/completion/CompletionList.vue'), meta: { title: '完工情况处理', cacheName: 'CompletionList' } }, |
| | | { path: 'completion/report', component: () => import('@/views/completion/CompletionReport.vue'), props: route => ({ workOrderId: route.query.workOrderId || null }), meta: { title: '完工信息上报', cacheName: 'CompletionReport' } }, |
| | | { path: 'acceptance/application', component: () => import('@/views/acceptance/AcceptanceList.vue'), props: { stage: 'APPLICATION' }, meta: { title: '验收申请', stage: 'APPLICATION', cacheName: 'AcceptanceList' } }, |
| | | { path: 'acceptance/inspector', component: () => import('@/views/acceptance/AcceptanceList.vue'), props: { stage: 'INSPECTOR' }, meta: { title: '质检员验收', stage: 'INSPECTOR', cacheName: 'AcceptanceList' } }, |
| | | { path: 'acceptance/leader', component: () => import('@/views/acceptance/AcceptanceList.vue'), props: { stage: 'LEADER' }, meta: { title: '质检负责人验收', stage: 'LEADER', cacheName: 'AcceptanceList' } }, |
| | | { path: 'acceptance/print', component: () => import('@/views/acceptance/AcceptanceList.vue'), props: { stage: 'PRINT' }, meta: { title: '完工验收打印输出', stage: 'PRINT', cacheName: 'AcceptanceList' } }, |
| | | { path: 'acceptance/rating', component: () => import('@/views/acceptance/AcceptanceList.vue'), props: { stage: 'RATING' }, meta: { title: '修后质量评级', stage: 'RATING', cacheName: 'AcceptanceList' } }, |
| | | { path: 'reports/:type', component: () => import('@/views/report/ReportPage.vue'), props: route => ({ type: route.params.type }), meta: { title: '统计报表', cacheName: 'ReportPage' } }, |
| | | { path: 'system/contractors', component: () => import('@/views/system/ContractorManage.vue'), meta: { title: '承修单位管理', cacheName: 'ContractorManage' } }, |
| | | { path: 'system/product-tree', component: () => import('@/views/system/TreeManage.vue'), props: { treeType: 'product' }, meta: { title: '产品结构树管理', treeType: 'product', cacheName: 'TreeManage' } }, |
| | | { path: 'system/base-equipment', component: () => import('@/views/system/TreeManage.vue'), props: { treeType: 'base' }, meta: { title: '设备基础库管理', treeType: 'base', cacheName: 'TreeManage' } }, |
| | | { path: 'system/engineering-majors', component: () => import('@/views/system/EngineeringMajorManage.vue'), meta: { title: '工程专业配置', cacheName: 'EngineeringMajorManage' } }, |
| | | { path: 'system/team-majors', component: () => import('@/views/system/TeamMajorManage.vue'), meta: { title: 'T队专业配置', cacheName: 'TeamMajorManage' } }, |
| | | { path: 'system/roles', component: () => import('@/views/system/RoleManage.vue'), meta: { title: '角色管理', cacheName: 'RoleManage' } }, |
| | | { path: 'system/menus', component: () => import('@/views/system/MenuManage.vue'), meta: { title: '菜单管理', cacheName: 'MenuManage' } }, |
| | | { path: 'system/menu-permissions', component: () => import('@/views/system/MenuPermission.vue'), meta: { title: '菜单权限', cacheName: 'MenuPermission' } }, |
| | | { path: 'system/three-member', component: () => import('@/views/system/ThreeMember.vue'), meta: { title: '三员管理', cacheName: 'ThreeMember' } } |
| | | ] |
| | | } |
| | | ] |
| | |
| | | </div> |
| | | |
| | | <div class="table-panel"> |
| | | <el-table :data="filteredRows" border stripe height="calc(100vh - 230px)"> |
| | | <el-table :data="filteredRows" border stripe height="calc(100vh - 270px)"> |
| | | <el-table-column prop="workOrderNo" label="派工单编号" width="155" fixed></el-table-column> |
| | | <el-table-column prop="status" label="验收状态" width="155" fixed> |
| | | <template slot-scope="{ row }"><el-tag size="mini" :type="acceptanceMeta(row.status).type">{{ acceptanceMeta(row.status).text }}</el-tag></template> |
| | |
| | | |
| | | export default { |
| | | name: 'AcceptanceList', |
| | | props: { |
| | | stage: { type: String, required: true } |
| | | }, |
| | | data() { |
| | | return { |
| | | rows: [], |
| | |
| | | } |
| | | }, |
| | | computed: { |
| | | stage() { return this.$route.meta.stage }, |
| | | pageTitle() { |
| | | return { APPLICATION: '验收申请', INSPECTOR: '质检员验收', LEADER: '质检负责人验收', PRINT: '完工验收打印输出', RATING: '修后质量评级' }[this.stage] |
| | | }, |
| | |
| | | } |
| | | }, |
| | | watch: { |
| | | '$route.path': 'onRouteChange' |
| | | stage: 'onRouteChange' |
| | | }, |
| | | created() { this.load() }, |
| | | methods: { |
| | |
| | | </div> |
| | | |
| | | <div class="table-panel"> |
| | | <el-table :data="rows" border stripe height="calc(100vh - 285px)" highlight-current-row @current-change="selectedRow = $event"> |
| | | <el-table :data="rows" border stripe height="calc(100vh - 325px)" highlight-current-row @current-change="selectedRow = $event"> |
| | | <el-table-column width="46" align="center"> |
| | | <template slot-scope="{ row }"><el-radio v-model="selectedId" :label="row.id" @change="selectRow(row)"><span></span></el-radio></template> |
| | | </el-table-column> |
| | |
| | | import api from '@/api' |
| | | export default { |
| | | name: 'CompletionReport', |
| | | props: { |
| | | workOrderId: { type: [String, Number], default: null } |
| | | }, |
| | | data() { |
| | | return { |
| | | workOrder: null, |
| | |
| | | }, |
| | | computed: { |
| | | routeWorkOrderId() { |
| | | const value = this.$route.query.workOrderId |
| | | const value = this.workOrderId |
| | | return value ? Number(value) : null |
| | | } |
| | | }, |
| | |
| | | <el-alert :closable="false" type="info" show-icon title="一个故障有多个承修单位时,将分别生成派工单;编号从“故障编号/01”开始依次编号。" style="margin-bottom:12px"></el-alert> |
| | | |
| | | <div class="table-panel"> |
| | | <el-table :data="rows" border stripe height="calc(100vh - 285px)" @selection-change="selection = $event"> |
| | | <el-table :data="rows" border stripe height="calc(100vh - 325px)" @selection-change="selection = $event"> |
| | | <el-table-column type="selection" width="46"></el-table-column> |
| | | <el-table-column prop="faultCode" label="故障编号" width="180" fixed></el-table-column> |
| | | <el-table-column prop="xNo" label="X号" width="64"></el-table-column> |
| | |
| | | </div> |
| | | |
| | | <div class="table-panel"> |
| | | <el-table :data="rows" border stripe height="calc(100vh - 355px)" :header-cell-style="{ textAlign: 'center' }" @selection-change="selection = $event"> |
| | | <el-table :data="rows" border stripe height="calc(100vh - 395px)" :header-cell-style="{ textAlign: 'center' }" @selection-change="selection = $event"> |
| | | <el-table-column type="selection" width="46" :selectable="row => canEdit(row)"></el-table-column> |
| | | <el-table-column prop="taskStatus" label="任务状态" width="145" fixed> |
| | | <template slot-scope="{ row }"><el-tag size="mini" :type="taskMeta(row.taskStatus).type">{{ taskMeta(row.taskStatus).text }}</el-tag></template> |
| | |
| | | </div> |
| | | |
| | | <div class="table-panel"> |
| | | <el-table :data="rows" border stripe height="calc(100vh - 230px)" :header-cell-style="{ textAlign: 'center' }" @selection-change="selection = $event"> |
| | | <el-table :data="rows" border stripe height="calc(100vh - 270px)" :header-cell-style="{ textAlign: 'center' }" @selection-change="selection = $event"> |
| | | <el-table-column type="selection" width="46"></el-table-column> |
| | | <el-table-column prop="taskStatus" label="任务状态" width="145" fixed> |
| | | <template slot-scope="{ row }"><el-tag size="mini" :type="taskMeta(row.taskStatus).type">{{ taskMeta(row.taskStatus).text }}</el-tag></template> |
| | |
| | | |
| | | export default { |
| | | name: 'SimpleApproval', |
| | | props: { |
| | | stage: { type: String, required: true } |
| | | }, |
| | | data() { |
| | | return { rows: [], selection: [], keyword: '', dialogVisible: false, saving: false, form: { result: 'PASS', opinion: '' } } |
| | | }, |
| | | computed: { |
| | | stage() { return this.$route.meta.stage }, |
| | | pageTitle() { |
| | | return { CMB: 'CMB领导审批', JCC_ASSISTANT: 'JCC助理审批', JCC_DIRECTOR: 'JCC处长审批' }[this.stage] |
| | | }, |
| | |
| | | </div> |
| | | |
| | | <div class="table-panel"> |
| | | <el-table :data="rows" border stripe height="calc(100vh - 355px)" :header-cell-style="{ textAlign: 'center' }" @selection-change="selection = $event"> |
| | | <el-table :data="rows" border stripe height="calc(100vh - 395px)" :header-cell-style="{ textAlign: 'center' }" @selection-change="selection = $event"> |
| | | <el-table-column type="selection" width="46" :selectable="canProcess"></el-table-column> |
| | | <el-table-column prop="taskStatus" label="任务状态" width="145" fixed> |
| | | <template slot-scope="{ row }"><el-tag size="mini" :type="taskMeta(row.taskStatus).type">{{ taskMeta(row.taskStatus).text }}</el-tag></template> |
| | |
| | | |
| | | export default { |
| | | name: 'ReportPage', |
| | | props: { |
| | | type: { type: String, required: true } |
| | | }, |
| | | data() { |
| | | return { stats: {}, rows: [], chart: null } |
| | | }, |
| | | computed: { |
| | | type() { return this.$route.params.type }, |
| | | title() { |
| | | return { |
| | | overview: '装备故障总体分析', |
| | |
| | | } |
| | | }, |
| | | watch: { |
| | | '$route.params.type': 'load' |
| | | type: 'load' |
| | | }, |
| | | mounted() { |
| | | this.load() |
| | |
| | | </div> |
| | | |
| | | <div class="table-panel"> |
| | | <el-table :data="filteredRows" border stripe height="calc(100vh - 205px)"> |
| | | <el-table :data="filteredRows" border stripe height="calc(100vh - 245px)"> |
| | | <el-table-column prop="sortNo" label="排序" width="80" align="center"></el-table-column> |
| | | <el-table-column label="常用" width="80" align="center"> |
| | | <template slot-scope="{ $index }"><el-tag v-if="$index < 7" size="mini" type="success">前7</el-tag><span v-else>-</span></template> |
| | |
| | | </div> |
| | | |
| | | <div class="table-panel"> |
| | | <el-table :data="filteredRows" border stripe height="calc(100vh - 205px)"> |
| | | <el-table :data="filteredRows" border stripe height="calc(100vh - 245px)"> |
| | | <el-table-column type="index" label="序号" width="70" align="center"></el-table-column> |
| | | <el-table-column prop="name" label="名称" min-width="240"></el-table-column> |
| | | <el-table-column label="操作" width="140" fixed="right"> |
| | |
| | | </script> |
| | | <style scoped> |
| | | .permission-grid { display: grid; grid-template-columns: 1fr 1fr 320px; gap: 12px; } |
| | | .permission-panel { min-height: calc(100vh - 180px); padding: 14px; border: 1px solid #dce4ec; border-radius: 4px; background: #fff; overflow: auto; } |
| | | .permission-panel { min-height: calc(100vh - 220px); padding: 14px; border: 1px solid #dce4ec; border-radius: 4px; background: #fff; overflow: auto; } |
| | | .panel-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; } |
| | | .panel-head h3 { margin: 0; color: #31465d; font-size: 15px; } |
| | | .intersection-list { display: flex; flex-wrap: wrap; gap: 6px; } |
| | |
| | | <el-button type="primary" icon="el-icon-plus" @click="openCreate">新增角色</el-button> |
| | | </div> |
| | | <div class="table-panel"> |
| | | <el-table :data="rows" border stripe height="calc(100vh - 150px)"> |
| | | <el-table :data="rows" border stripe height="calc(100vh - 190px)"> |
| | | <el-table-column prop="sortNo" label="排序" width="80" align="center"></el-table-column> |
| | | <el-table-column prop="name" label="角色名称" min-width="180"></el-table-column> |
| | | <el-table-column prop="code" label="角色编码" width="170"></el-table-column> |
| | |
| | | </div> |
| | | |
| | | <div class="table-panel"> |
| | | <el-table :data="filteredRows" border stripe height="calc(100vh - 205px)"> |
| | | <el-table :data="filteredRows" border stripe height="calc(100vh - 245px)"> |
| | | <el-table-column type="index" label="序号" width="70" align="center"></el-table-column> |
| | | <el-table-column prop="name" label="名称" min-width="240"></el-table-column> |
| | | <el-table-column prop="category" label="分类" width="120" align="center"> |
| | |
| | | import api from '@/api' |
| | | export default { |
| | | name: 'TreeManage', |
| | | props: { |
| | | treeType: { type: String, required: true } |
| | | }, |
| | | data() { |
| | | return { tree: [], keyword: '', form: {}, saving: false, baseDevices: [] } |
| | | }, |
| | | computed: { |
| | | treeType() { return this.$route.meta.treeType }, |
| | | isProduct() { return this.treeType === 'product' }, |
| | | maxLevel() { return this.isProduct ? 5 : 3 }, |
| | | levelNames() { return this.isProduct ? ['型号', 'X号', '一级系统', '二级系统', '设备'] : ['一级系统', '二级系统', '设备'] } |
| | | }, |
| | | watch: { |
| | | keyword(value) { this.$refs.tree && this.$refs.tree.filter(value) }, |
| | | '$route.meta.treeType'() { this.form = {}; this.load() } |
| | | treeType() { this.form = {}; this.load() } |
| | | }, |
| | | created() { this.load() }, |
| | | methods: { |
| | |
| | | </script> |
| | | <style scoped> |
| | | .tree-layout { display: grid; grid-template-columns: 420px minmax(0, 1fr); gap: 12px; } |
| | | .tree-panel, .detail-panel { min-height: calc(100vh - 140px); padding: 14px; border: 1px solid #dce4ec; border-radius: 4px; background: #fff; } |
| | | .tree-panel, .detail-panel { min-height: calc(100vh - 180px); padding: 14px; border: 1px solid #dce4ec; border-radius: 4px; background: #fff; } |
| | | .tree-node { display: flex; align-items: center; gap: 7px; } |
| | | .tree-node small { color: #95a2b0; } |
| | | .level-badge { display: inline-flex; align-items: center; justify-content: center; min-width: 50px; height: 20px; padding: 0 5px; border-radius: 3px; color: #2f658f; background: #e9f3fb; font-size: 11px; } |