DeviceUpgrade.vue 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. <template>
  2. <div class="device-action-container">
  3. <div class="content-body">
  4. <div class="form-row">
  5. <label class="form-label">升级文件</label>
  6. <div class="upload-wrapper">
  7. <div class="mock-input" @click="triggerUpload" :class="{ 'is-disabled': isLoading }">
  8. <span class="file-name" v-if="fileName">{{ fileName }}</span>
  9. <span class="placeholder" v-else></span>
  10. <i class="el-icon-plus add-icon">+</i>
  11. </div>
  12. <input type="file" ref="fileInput" class="hidden-file-input" @change="handleFileChange"
  13. accept=".bin,.zip,.tar.gz" :disabled="isLoading" />
  14. <button class="btn-primary upload-btn" @click="triggerUpload" :disabled="isLoading">
  15. 上传
  16. </button>
  17. </div>
  18. </div>
  19. </div>
  20. <div class="dialog-footer">
  21. <div class="action-text">
  22. 操作:<span class="highlight">升级</span>
  23. </div>
  24. <div class="action-buttons">
  25. <button class="btn-default" @click="cancel" :disabled="isLoading">取消</button>
  26. <button class="btn-primary" @click="confirm" :disabled="isLoading">
  27. <i v-if="isLoading" class="loading-spinner"></i>
  28. {{ isLoading ? '上传中...' : '确认' }}
  29. </button>
  30. </div>
  31. </div>
  32. </div>
  33. </template>
  34. <script>
  35. export default {
  36. name: 'DeviceUpgrade',
  37. props: {
  38. nodeId: {
  39. type: [String, Number],
  40. default: ''
  41. },
  42. onClose: {
  43. type: Function,
  44. default: null
  45. }
  46. },
  47. data() {
  48. return {
  49. fileName: '',
  50. isLoading: false // 新增 loading 状态
  51. };
  52. },
  53. methods: {
  54. triggerUpload() {
  55. if (this.isLoading) return; // 上传中禁止重新选文件
  56. this.$refs.fileInput.click();
  57. },
  58. handleFileChange(event) {
  59. const file = event.target.files[0];
  60. if (file) {
  61. this.fileName = file.name;
  62. }
  63. },
  64. cancel() {
  65. if (this.onClose) this.onClose();
  66. },
  67. // 将 confirm 改为异步方法
  68. async confirm() {
  69. if (!this.fileName) {
  70. alert('请先选择升级文件'); // 建议替换为 Element UI 的 this.$message.warning
  71. return;
  72. }
  73. this.isLoading = true; // 开启 loading
  74. try {
  75. // 模拟 API 请求 (用 setTimeout 模拟网络延迟 2 秒)
  76. // 真实业务中请替换为:await this.$http.post('/api/upgrade', formData)
  77. await new Promise(resolve => setTimeout(resolve, 2000));
  78. console.log(`节点ID: ${this.nodeId}, 文件: ${this.fileName} 升级成功`);
  79. // 可选:提示成功 this.$message.success('升级任务下发成功');
  80. if (this.onClose) this.onClose(); // 请求成功后才关闭弹窗
  81. } catch (error) {
  82. console.error('升级失败', error);
  83. // 可选:提示失败 this.$message.error('升级失败,请重试');
  84. } finally {
  85. this.isLoading = false; // 无论成功失败,重置 loading 状态
  86. }
  87. }
  88. }
  89. };
  90. </script>
  91. <style scoped>
  92. /* 此处保留上一版的全部 CSS,只在最下面追加 Loading 和 Disabled 相关的样式 */
  93. * {
  94. box-sizing: border-box;
  95. }
  96. .device-action-container {
  97. display: flex;
  98. flex-direction: column;
  99. height: 100%;
  100. width: 100%;
  101. color: #c0c4cc;
  102. font-size: 14px;
  103. }
  104. .content-body {
  105. flex: 1;
  106. display: flex;
  107. align-items: center;
  108. padding: 0 20px;
  109. }
  110. .form-row {
  111. display: flex;
  112. align-items: center;
  113. width: 100%;
  114. }
  115. .form-label {
  116. width: 70px;
  117. text-align: right;
  118. margin-right: 15px;
  119. white-space: nowrap;
  120. flex-shrink: 0;
  121. }
  122. .upload-wrapper {
  123. display: flex;
  124. flex: 1;
  125. align-items: center;
  126. gap: 10px;
  127. min-width: 0;
  128. }
  129. .mock-input {
  130. flex: 1;
  131. min-width: 0;
  132. height: 32px;
  133. background: rgba(16, 36, 70, 0.5);
  134. border: 1px solid #2a4c85;
  135. border-radius: 4px;
  136. display: flex;
  137. justify-content: space-between;
  138. align-items: center;
  139. padding: 0 10px;
  140. cursor: pointer;
  141. transition: border-color 0.2s;
  142. }
  143. .mock-input:hover:not(.is-disabled) {
  144. border-color: #3e73ff;
  145. }
  146. .file-name {
  147. color: #fff;
  148. flex: 1;
  149. margin-right: 10px;
  150. overflow: hidden;
  151. text-overflow: ellipsis;
  152. white-space: nowrap;
  153. }
  154. .add-icon {
  155. color: #8fb3ff;
  156. font-size: 16px;
  157. font-weight: bold;
  158. flex-shrink: 0;
  159. }
  160. .hidden-file-input {
  161. display: none;
  162. }
  163. .upload-btn {
  164. flex-shrink: 0;
  165. white-space: nowrap;
  166. }
  167. .dialog-footer {
  168. border-top: 1px solid rgba(42, 76, 133, 0.5);
  169. padding: 12px 20px;
  170. display: flex;
  171. justify-content: space-between;
  172. align-items: center;
  173. flex-shrink: 0;
  174. }
  175. .action-text {
  176. color: #c0c4cc;
  177. white-space: nowrap;
  178. }
  179. .action-text .highlight {
  180. color: #1e6fff;
  181. }
  182. .action-buttons {
  183. display: flex;
  184. gap: 15px;
  185. flex-shrink: 0;
  186. }
  187. button {
  188. height: 32px;
  189. padding: 0 20px;
  190. border-radius: 4px;
  191. cursor: pointer;
  192. font-size: 14px;
  193. outline: none;
  194. transition: all 0.2s;
  195. display: inline-flex;
  196. align-items: center;
  197. justify-content: center;
  198. }
  199. .btn-primary {
  200. background: #1e6fff;
  201. border: 1px solid #1e6fff;
  202. color: #fff;
  203. }
  204. .btn-primary:hover:not(:disabled) {
  205. background: #3e83ff;
  206. border-color: #3e83ff;
  207. }
  208. .btn-default {
  209. background: transparent;
  210. border: 1px solid #2a4c85;
  211. color: #c0c4cc;
  212. }
  213. .btn-default:hover:not(:disabled) {
  214. border-color: #3e73ff;
  215. color: #fff;
  216. }
  217. /* ================= 新增:Disabled 与 Loading 样式 ================= */
  218. .is-disabled {
  219. cursor: not-allowed;
  220. opacity: 0.6;
  221. }
  222. button:disabled {
  223. cursor: not-allowed;
  224. opacity: 0.6;
  225. /* 防止被禁用时发生位移或颜色突变 */
  226. }
  227. /* 简单的 CSS 旋转动画 */
  228. .loading-spinner {
  229. display: inline-block;
  230. width: 14px;
  231. height: 14px;
  232. border: 2px solid rgba(255, 255, 255, 0.3);
  233. border-radius: 50%;
  234. border-top-color: #ffffff;
  235. animation: spin 1s linear infinite;
  236. margin-right: 6px;
  237. }
  238. @keyframes spin {
  239. to {
  240. transform: rotate(360deg);
  241. }
  242. }
  243. </style>