|
|
@@ -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>
|