Просмотр исходного кода

新增DeviceUpgrade.vue升级组件和DeviceRestart.vue重启组件;修改DashboardLayout注册新增的两个组件;修改CrossingListPanel组件的重启和升级按钮功能;

画安 месяцев назад: 2
Родитель
Сommit
a4b4c90731

+ 37 - 0
src/components/ui/CrossingListPanel.vue

@@ -53,6 +53,7 @@ export default {
         SignalTimingChart,
         SignalTimingChart,
         TechPagination
         TechPagination
     },
     },
+    inject: { dialogManager: { default: null } },
     props: {
     props: {
         onViewDetail: {
         onViewDetail: {
             type: Function,
             type: Function,
@@ -198,8 +199,44 @@ export default {
         handleAction(type, row) {
         handleAction(type, row) {
             if (type === 'upgrade') {
             if (type === 'upgrade') {
                 console.log('执行升级:', row.name);
                 console.log('执行升级:', row.name);
+                if (this.dialogManager) {
+                    const dialogId = 'device-upgrade-' + row.id;
+                    this.dialogManager.openDialog({
+                        id: dialogId,
+                        title: '设备升级',
+                        component: 'DeviceUpgrade',
+                        width: 400,
+                        height: 200,
+                        center: true,
+                        enableDblclickExpand: false,
+                        noPadding: true,
+                        data: {
+                            nodeName: row.name,
+                            nodeId: row.id,
+                            onClose: () => this.dialogManager.closeDialog(dialogId)
+                        }
+                    });
+                }
             } else if (type === 'restart') {
             } else if (type === 'restart') {
                 console.log('执行重启:', row.name);
                 console.log('执行重启:', row.name);
+                if (this.dialogManager) {
+                    const dialogId = 'device-restart-' + row.id;
+                    this.dialogManager.openDialog({
+                        id: dialogId,
+                        title: '设备重启',
+                        component: 'DeviceRestart',
+                        width: 400,
+                        height: 200,
+                        center: true,
+                        enableDblclickExpand: false,
+                        noPadding: true,
+                        data: {
+                            nodeName: row.name,
+                            nodeId: row.id,
+                            onClose: () => this.dialogManager.closeDialog(dialogId)
+                        }
+                    });
+                }
             } else if (type === 'view') {
             } else if (type === 'view') {
                 console.log('查看详情:', row.name);
                 console.log('查看详情:', row.name);
                 if (this.onViewDetail) {
                 if (this.onViewDetail) {

+ 180 - 0
src/components/ui/DeviceRestart.vue

@@ -0,0 +1,180 @@
+<template>
+    <div class="device-action-container">
+        <div class="content-body">
+            <div class="confirm-message">
+                确认要重启{{ nodeName }}吗?
+            </div>
+        </div>
+
+        <div class="dialog-footer">
+            <div class="action-text">
+                操作:<span class="highlight">重启</span>
+            </div>
+            <div class="action-buttons">
+                <button class="btn-default" @click="cancel" :disabled="isLoading">取消</button>
+                <button class="btn-primary" @click="confirm" :disabled="isLoading">
+                    <i v-if="isLoading" class="loading-spinner"></i>
+                    {{ isLoading ? '重启中...' : '确认' }}
+                </button>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+export default {
+    name: 'DeviceRestart',
+    props: {
+        nodeName: {
+            type: String,
+            default: '当前设备'
+        },
+        nodeId: {
+            type: [String, Number],
+            default: ''
+        },
+        onClose: {
+            type: Function,
+            default: null
+        }
+    },
+    data() {
+        return {
+            isLoading: false // 新增 loading 状态
+        };
+    },
+    methods: {
+        cancel() {
+            if (this.onClose) this.onClose();
+        },
+        async confirm() {
+            this.isLoading = true;
+
+            try {
+                // 模拟 API 请求 (延迟 1.5 秒)
+                // 真实业务中替换为:await this.$http.post('/api/restart', { id: this.nodeId })
+                await new Promise(resolve => setTimeout(resolve, 1500));
+
+                console.log(`节点: ${this.nodeName} 已发送重启指令`);
+
+                if (this.onClose) this.onClose(); // 请求成功后关闭
+            } catch (error) {
+                console.error('重启失败', error);
+            } finally {
+                this.isLoading = false;
+            }
+        }
+    }
+};
+</script>
+
+<style scoped>
+/* 此处保留上一版的全部 CSS,只在最下面追加 Loading 和 Disabled 相关的样式 */
+* {
+    box-sizing: border-box;
+}
+
+.device-action-container {
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+    width: 100%;
+    color: #c0c4cc;
+    font-size: 14px;
+}
+
+.content-body {
+    flex: 1;
+    display: flex;
+    align-items: center;
+    padding: 0 30px;
+}
+
+.confirm-message {
+    color: #ffffff;
+    font-size: 15px;
+    letter-spacing: 1px;
+}
+
+.dialog-footer {
+    border-top: 1px solid rgba(42, 76, 133, 0.5);
+    padding: 12px 20px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    flex-shrink: 0;
+}
+
+.action-text {
+    color: #c0c4cc;
+    white-space: nowrap;
+}
+
+.action-text .highlight {
+    color: #1e6fff;
+}
+
+.action-buttons {
+    display: flex;
+    gap: 15px;
+    flex-shrink: 0;
+}
+
+button {
+    height: 32px;
+    padding: 0 20px;
+    border-radius: 4px;
+    cursor: pointer;
+    font-size: 14px;
+    outline: none;
+    transition: all 0.2s;
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+}
+
+.btn-primary {
+    background: #1e6fff;
+    border: 1px solid #1e6fff;
+    color: #fff;
+}
+
+.btn-primary:hover:not(:disabled) {
+    background: #3e83ff;
+    border-color: #3e83ff;
+}
+
+.btn-default {
+    background: transparent;
+    border: 1px solid #2a4c85;
+    color: #c0c4cc;
+}
+
+.btn-default:hover:not(:disabled) {
+    border-color: #3e73ff;
+    color: #fff;
+}
+
+/* ================= 新增:Disabled 与 Loading 样式 ================= */
+button:disabled {
+    cursor: not-allowed;
+    opacity: 0.6;
+}
+
+.loading-spinner {
+    display: inline-block;
+    width: 14px;
+    height: 14px;
+    border: 2px solid rgba(255, 255, 255, 0.3);
+    border-radius: 50%;
+    border-top-color: #ffffff;
+    animation: spin 1s linear infinite;
+    margin-right: 6px;
+}
+
+@keyframes spin {
+    to {
+        transform: rotate(360deg);
+    }
+}
+</style>

+ 274 - 0
src/components/ui/DeviceUpgrade.vue

@@ -0,0 +1,274 @@
+<template>
+    <div class="device-action-container">
+        <div class="content-body">
+            <div class="form-row">
+                <label class="form-label">升级文件</label>
+                <div class="upload-wrapper">
+                    <div class="mock-input" @click="triggerUpload" :class="{ 'is-disabled': isLoading }">
+                        <span class="file-name" v-if="fileName">{{ fileName }}</span>
+                        <span class="placeholder" v-else></span>
+                        <i class="el-icon-plus add-icon">+</i>
+                    </div>
+                    <input type="file" ref="fileInput" class="hidden-file-input" @change="handleFileChange"
+                        accept=".bin,.zip,.tar.gz" :disabled="isLoading" />
+                    <button class="btn-primary upload-btn" @click="triggerUpload" :disabled="isLoading">
+                        上传
+                    </button>
+                </div>
+            </div>
+        </div>
+
+        <div class="dialog-footer">
+            <div class="action-text">
+                操作:<span class="highlight">升级</span>
+            </div>
+            <div class="action-buttons">
+                <button class="btn-default" @click="cancel" :disabled="isLoading">取消</button>
+                <button class="btn-primary" @click="confirm" :disabled="isLoading">
+                    <i v-if="isLoading" class="loading-spinner"></i>
+                    {{ isLoading ? '上传中...' : '确认' }}
+                </button>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+export default {
+    name: 'DeviceUpgrade',
+    props: {
+        nodeId: {
+            type: [String, Number],
+            default: ''
+        },
+        onClose: {
+            type: Function,
+            default: null
+        }
+    },
+    data() {
+        return {
+            fileName: '',
+            isLoading: false // 新增 loading 状态
+        };
+    },
+    methods: {
+        triggerUpload() {
+            if (this.isLoading) return; // 上传中禁止重新选文件
+            this.$refs.fileInput.click();
+        },
+        handleFileChange(event) {
+            const file = event.target.files[0];
+            if (file) {
+                this.fileName = file.name;
+            }
+        },
+        cancel() {
+            if (this.onClose) this.onClose();
+        },
+        // 将 confirm 改为异步方法
+        async confirm() {
+            if (!this.fileName) {
+                alert('请先选择升级文件'); // 建议替换为 Element UI 的 this.$message.warning
+                return;
+            }
+
+            this.isLoading = true; // 开启 loading
+
+            try {
+                // 模拟 API 请求 (用 setTimeout 模拟网络延迟 2 秒)
+                // 真实业务中请替换为:await this.$http.post('/api/upgrade', formData)
+                await new Promise(resolve => setTimeout(resolve, 2000));
+
+                console.log(`节点ID: ${this.nodeId}, 文件: ${this.fileName} 升级成功`);
+                // 可选:提示成功 this.$message.success('升级任务下发成功');
+
+                if (this.onClose) this.onClose(); // 请求成功后才关闭弹窗
+            } catch (error) {
+                console.error('升级失败', error);
+                // 可选:提示失败 this.$message.error('升级失败,请重试');
+            } finally {
+                this.isLoading = false; // 无论成功失败,重置 loading 状态
+            }
+        }
+    }
+};
+</script>
+
+<style scoped>
+/* 此处保留上一版的全部 CSS,只在最下面追加 Loading 和 Disabled 相关的样式 */
+* {
+    box-sizing: border-box;
+}
+
+.device-action-container {
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+    width: 100%;
+    color: #c0c4cc;
+    font-size: 14px;
+}
+
+.content-body {
+    flex: 1;
+    display: flex;
+    align-items: center;
+    padding: 0 20px;
+}
+
+.form-row {
+    display: flex;
+    align-items: center;
+    width: 100%;
+}
+
+.form-label {
+    width: 70px;
+    text-align: right;
+    margin-right: 15px;
+    white-space: nowrap;
+    flex-shrink: 0;
+}
+
+.upload-wrapper {
+    display: flex;
+    flex: 1;
+    align-items: center;
+    gap: 10px;
+    min-width: 0;
+}
+
+.mock-input {
+    flex: 1;
+    min-width: 0;
+    height: 32px;
+    background: rgba(16, 36, 70, 0.5);
+    border: 1px solid #2a4c85;
+    border-radius: 4px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 0 10px;
+    cursor: pointer;
+    transition: border-color 0.2s;
+}
+
+.mock-input:hover:not(.is-disabled) {
+    border-color: #3e73ff;
+}
+
+.file-name {
+    color: #fff;
+    flex: 1;
+    margin-right: 10px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+}
+
+.add-icon {
+    color: #8fb3ff;
+    font-size: 16px;
+    font-weight: bold;
+    flex-shrink: 0;
+}
+
+.hidden-file-input {
+    display: none;
+}
+
+.upload-btn {
+    flex-shrink: 0;
+    white-space: nowrap;
+}
+
+.dialog-footer {
+    border-top: 1px solid rgba(42, 76, 133, 0.5);
+    padding: 12px 20px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    flex-shrink: 0;
+}
+
+.action-text {
+    color: #c0c4cc;
+    white-space: nowrap;
+}
+
+.action-text .highlight {
+    color: #1e6fff;
+}
+
+.action-buttons {
+    display: flex;
+    gap: 15px;
+    flex-shrink: 0;
+}
+
+button {
+    height: 32px;
+    padding: 0 20px;
+    border-radius: 4px;
+    cursor: pointer;
+    font-size: 14px;
+    outline: none;
+    transition: all 0.2s;
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+}
+
+.btn-primary {
+    background: #1e6fff;
+    border: 1px solid #1e6fff;
+    color: #fff;
+}
+
+.btn-primary:hover:not(:disabled) {
+    background: #3e83ff;
+    border-color: #3e83ff;
+}
+
+.btn-default {
+    background: transparent;
+    border: 1px solid #2a4c85;
+    color: #c0c4cc;
+}
+
+.btn-default:hover:not(:disabled) {
+    border-color: #3e73ff;
+    color: #fff;
+}
+
+/* ================= 新增:Disabled 与 Loading 样式 ================= */
+.is-disabled {
+    cursor: not-allowed;
+    opacity: 0.6;
+}
+
+button:disabled {
+    cursor: not-allowed;
+    opacity: 0.6;
+    /* 防止被禁用时发生位移或颜色突变 */
+}
+
+/* 简单的 CSS 旋转动画 */
+.loading-spinner {
+    display: inline-block;
+    width: 14px;
+    height: 14px;
+    border: 2px solid rgba(255, 255, 255, 0.3);
+    border-radius: 50%;
+    border-top-color: #ffffff;
+    animation: spin 1s linear infinite;
+    margin-right: 6px;
+}
+
+@keyframes spin {
+    to {
+        transform: rotate(360deg);
+    }
+}
+</style>

+ 4 - 0
src/layouts/DashboardLayout.vue

@@ -84,6 +84,8 @@ import CrossingDetailPanel from '@/components/ui/CrossingDetailPanel.vue';
 import CrossingListPanel from '@/components/ui/CrossingListPanel.vue';
 import CrossingListPanel from '@/components/ui/CrossingListPanel.vue';
 import SecurityRoutePanelSwitch from '@/components/ui/SecurityRoutePanelSwitch.vue';
 import SecurityRoutePanelSwitch from '@/components/ui/SecurityRoutePanelSwitch.vue';
 import SecurityRoutePanelSwitchSmall from '@/components/ui/SecurityRoutePanelSwitchSmall.vue';
 import SecurityRoutePanelSwitchSmall from '@/components/ui/SecurityRoutePanelSwitchSmall.vue';
+import DeviceRestart from '@/components/ui/DeviceRestart.vue';
+import DeviceUpgrade from '@/components/ui/DeviceUpgrade.vue';
 
 
 export default {
 export default {
     name: 'DashboardLayout',
     name: 'DashboardLayout',
@@ -101,6 +103,8 @@ export default {
         CrossingListPanel,
         CrossingListPanel,
         SecurityRoutePanelSwitch,
         SecurityRoutePanelSwitch,
         SecurityRoutePanelSwitchSmall,
         SecurityRoutePanelSwitchSmall,
+        DeviceRestart,
+        DeviceUpgrade
     },
     },
     provide() {
     provide() {
         return {
         return {