FirmwareDialog.vue 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. <template>
  2. <el-dialog :title="title" :visible.sync="dialogVisible" :close-on-click-modal="false" @close="handleClose"
  3. @open="handleOpen">
  4. <el-form ref="form" :model="form" :rules="rules" label-width="auto">
  5. <el-form-item :label="$t('firmwareDialog.firmwareName')" prop="firmwareName">
  6. <el-input v-model="form.firmwareName" :placeholder="$t('firmwareDialog.firmwareNamePlaceholder')"></el-input>
  7. </el-form-item>
  8. <el-form-item :label="$t('firmwareDialog.firmwareType')" prop="type">
  9. <el-select v-model="form.type" :placeholder="$t('firmwareDialog.firmwareTypePlaceholder')" style="width: 100%;"
  10. filterable :disabled="isTypeDisabled">
  11. <el-option v-for="item in firmwareTypes" :key="item.key" :label="item.name" :value="item.key"></el-option>
  12. </el-select>
  13. </el-form-item>
  14. <el-form-item :label="$t('firmwareDialog.version')" prop="version">
  15. <el-input v-model="form.version" :placeholder="$t('firmwareDialog.versionPlaceholder')"></el-input>
  16. </el-form-item>
  17. <el-form-item :label="$t('firmwareDialog.firmwareFile')" prop="firmwarePath">
  18. <el-upload ref="upload" class="upload-demo" action="#" :http-request="handleUpload"
  19. :before-upload="beforeUpload" :accept="'.bin,.apk,.wav'" :limit="1" :multiple="false" :auto-upload="true"
  20. :on-remove="handleRemove">
  21. <el-button size="small" type="primary">{{ $t('firmwareDialog.clickUpload') }}</el-button>
  22. <div slot="tip" class="el-upload__tip">{{ $t('firmwareDialog.uploadTip') }}</div>
  23. </el-upload>
  24. <el-progress v-if="isUploading || uploadStatus === 'success'" :percentage="uploadProgress"
  25. :status="uploadStatus"></el-progress>
  26. <div class="hint-text">
  27. <span>{{ $t('firmwareDialog.uploadHint') }}</span>
  28. </div>
  29. </el-form-item>
  30. <el-form-item :label="$t('firmwareDialog.remark')" prop="remark">
  31. <el-input type="textarea" v-model="form.remark"
  32. :placeholder="$t('firmwareDialog.remarkPlaceholder')"></el-input>
  33. </el-form-item>
  34. </el-form>
  35. <div slot="footer" class="dialog-footer">
  36. <el-button @click="handleCancel">{{ $t('button.cancel') }}</el-button>
  37. <el-button type="primary" @click="handleSubmit">{{ $t('button.save') }}</el-button>
  38. </div>
  39. </el-dialog>
  40. </template>
  41. <script>
  42. import Api from '@/apis/api';
  43. export default {
  44. name: 'FirmwareDialog',
  45. props: {
  46. visible: {
  47. type: Boolean,
  48. default: false
  49. },
  50. title: {
  51. type: String,
  52. default: ''
  53. },
  54. form: {
  55. type: Object,
  56. default: () => ({})
  57. },
  58. firmwareTypes: {
  59. type: Array,
  60. default: () => []
  61. }
  62. },
  63. data() {
  64. return {
  65. uploadProgress: 0,
  66. uploadStatus: '',
  67. isUploading: false,
  68. dialogVisible: this.visible,
  69. rules: {
  70. firmwareName: [
  71. { required: true, message: this.$t('firmwareDialog.requiredFirmwareName'), trigger: 'blur' }
  72. ],
  73. type: [
  74. { required: true, message: this.$t('firmwareDialog.requiredFirmwareType'), trigger: 'change' }
  75. ],
  76. version: [
  77. { required: true, message: this.$t('firmwareDialog.requiredVersion'), trigger: 'blur' },
  78. { pattern: /^\d+\.\d+\.\d+$/, message: this.$t('firmwareDialog.versionFormatError'), trigger: 'blur' }
  79. ],
  80. firmwarePath: [
  81. { required: false, message: this.$t('firmwareDialog.requiredFirmwareFile'), trigger: 'change' }
  82. ]
  83. }
  84. }
  85. },
  86. computed: {
  87. isTypeDisabled() {
  88. // 如果有id,说明是编辑模式,禁用类型选择
  89. return !!this.form.id
  90. }
  91. },
  92. created() {
  93. // 移除 getDictDataByType 调用
  94. },
  95. watch: {
  96. visible(val) {
  97. this.dialogVisible = val;
  98. },
  99. dialogVisible(val) {
  100. this.$emit('update:visible', val);
  101. },
  102. },
  103. methods: {
  104. // 移除 getFirmwareTypes 方法
  105. handleClose() {
  106. this.dialogVisible = false;
  107. this.$emit('cancel');
  108. },
  109. handleCancel() {
  110. this.$refs.form.clearValidate();
  111. this.$emit('cancel');
  112. },
  113. handleSubmit() {
  114. this.$refs.form.validate(valid => {
  115. if (valid) {
  116. // 如果是新增模式且没有上传文件,则提示错误
  117. if (!this.form.id && !this.form.firmwarePath) {
  118. this.$message.error(this.$t('firmwareDialog.requiredFirmwareFile'))
  119. return
  120. }
  121. // 提交成功后将关闭对话框的逻辑交给父组件处理
  122. this.$emit('submit', this.form)
  123. }
  124. })
  125. },
  126. beforeUpload(file) {
  127. const isValidSize = file.size / 1024 / 1024 < 100
  128. const isValidType = ['.bin', '.apk'].some(ext => file.name.toLowerCase().endsWith(ext))
  129. if (!isValidType) {
  130. this.$message.error(this.$t('firmwareDialog.invalidFileType'))
  131. return false
  132. }
  133. if (!isValidSize) {
  134. this.$message.error(this.$t('firmwareDialog.invalidFileSize'))
  135. return false
  136. }
  137. return true
  138. },
  139. handleUpload(options) {
  140. const { file } = options
  141. this.uploadProgress = 0
  142. this.uploadStatus = ''
  143. this.isUploading = true
  144. // 使用setTimeout实现简单的0-50%过渡
  145. const timer = setTimeout(() => {
  146. if (this.uploadProgress < 50) { // 只有当进度小于50%时才设置
  147. this.uploadProgress = 50
  148. }
  149. }, 1000)
  150. Api.ota.uploadFirmware(file, (res) => {
  151. clearTimeout(timer) // 清除定时器
  152. res = res.data
  153. if (res.code === 0) {
  154. this.form.firmwarePath = res.data
  155. this.form.size = file.size
  156. this.uploadProgress = 100
  157. this.uploadStatus = 'success'
  158. this.$message.success(this.$t('firmwareDialog.uploadSuccess'))
  159. // 延迟2秒后隐藏进度条
  160. setTimeout(() => {
  161. this.isUploading = false
  162. }, 2000)
  163. } else {
  164. this.uploadStatus = 'exception'
  165. this.$message.error(res.msg || this.$t('firmwareDialog.uploadFailed'))
  166. this.isUploading = false
  167. }
  168. }, (progressEvent) => {
  169. if (progressEvent.total) {
  170. const progress = Math.round((progressEvent.loaded * 100) / progressEvent.total)
  171. // 只有当进度大于50%时才更新
  172. if (progress > 50) {
  173. this.uploadProgress = progress
  174. }
  175. // 如果上传完成但还没收到成功响应,保持进度条显示
  176. if (progress === 100) {
  177. this.uploadStatus = ''
  178. }
  179. }
  180. })
  181. },
  182. handleRemove() {
  183. this.form.firmwarePath = ''
  184. this.form.size = 0
  185. this.uploadProgress = 0
  186. this.uploadStatus = ''
  187. this.isUploading = false
  188. },
  189. handleOpen() {
  190. // 重置上传相关状态
  191. this.uploadProgress = 0
  192. this.uploadStatus = ''
  193. this.isUploading = false
  194. // 重置表单中的文件相关字段
  195. if (!this.form.id) { // 只在新增时重置
  196. this.form.firmwarePath = ''
  197. this.form.size = 0
  198. }
  199. // 无论是否编辑模式,都重置上传组件
  200. this.$nextTick(() => {
  201. if (this.$refs.upload) {
  202. this.$refs.upload.clearFiles()
  203. }
  204. })
  205. }
  206. }
  207. }
  208. </script>
  209. <style lang="scss" scoped>
  210. ::v-deep .el-dialog {
  211. border-radius: 20px;
  212. }
  213. .upload-demo {
  214. text-align: left;
  215. }
  216. .el-upload__tip {
  217. line-height: 1.2;
  218. padding-top: 2%;
  219. color: #909399;
  220. }
  221. .hint-text {
  222. display: flex;
  223. align-items: center;
  224. gap: 8px;
  225. font-size: 14px;
  226. }
  227. </style>