From 8665daa7aa863f96d0d8f27c167edae1efc38022 Mon Sep 17 00:00:00 2001
From: zjk <852185829@qq.com>
Date: 星期五, 31 七月 2026 14:46:19 +0800
Subject: [PATCH] Merge branch 'master' into dev

---
 ruoyi-ui/src/views/visitor-admin/system/version.vue |  155 ++++++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 138 insertions(+), 17 deletions(-)

diff --git a/ruoyi-ui/src/views/visitor-admin/system/version.vue b/ruoyi-ui/src/views/visitor-admin/system/version.vue
index 8c42ae2..4b20bb6 100644
--- a/ruoyi-ui/src/views/visitor-admin/system/version.vue
+++ b/ruoyi-ui/src/views/visitor-admin/system/version.vue
@@ -13,20 +13,28 @@
             <th>版本号</th>
             <th>更新内容</th>
             <th>创建时间</th>
-            <th v-if="!isMiddlePlatformReadonly && $auth.hasPermi('visitor:version:remove')">操作</th>
+            <th>操作</th>
           </tr>
         </thead>
         <tbody>
           <tr v-for="(row, index) in versionList" :key="row.updateId || index">
             <td>{{ index + 1 }}</td>
-            <td>v{{ row.versionNo }}</td>
+            <td>
+              {{ formatVersionNo(row.versionNo) }}
+              <el-tag v-if="row.latest" size="mini" type="success">最新</el-tag>
+            </td>
             <td class="version-content-cell">
               <span class="version-content-text">{{ row.updateContent || '-' }}</span>
             </td>
             <td>{{ parseTime(row.createTime) }}</td>
-            <td v-if="!isMiddlePlatformReadonly && $auth.hasPermi('visitor:version:remove')">
+            <td>
               <div class="table-actions">
-                <button v-hasPermi="['visitor:version:remove']" type="button" @click="handleDelete(row)">删除</button>
+                <button type="button" @click="handleView(row)">查看</button>
+                <button
+                  v-if="!row.latest && !isMiddlePlatformReadonly && $auth.hasPermi('visitor:version:remove')"
+                  type="button"
+                  @click="handleDelete(row)"
+                >删除</button>
               </div>
             </td>
           </tr>
@@ -36,36 +44,70 @@
       <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
     </section>
 
-    <el-dialog title="新建" :visible.sync="open" width="482px" append-to-body>
+    <el-dialog title="版本详情" :visible.sync="detailOpen" width="560px" append-to-body>
+      <el-form label-width="100px">
+        <el-form-item label="版本号">
+          <span>{{ formatVersionNo(detail.versionNo) }}</span>
+          <el-tag v-if="detail.latest" size="mini" type="success">最新</el-tag>
+        </el-form-item>
+        <el-form-item label="更新内容">
+          <div style="white-space: pre-wrap; word-break: break-all;">{{ detail.updateContent || '-' }}</div>
+        </el-form-item>
+        <el-form-item label="安装包">{{ detail.packageName || detail.packageUrl || '-' }}</el-form-item>
+        <el-form-item label="强制更新">{{ detail.forceUpdate ? '是' : '否' }}</el-form-item>
+        <el-form-item label="创建时间">{{ parseTime(detail.createTime) || '-' }}</el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="detailOpen = false">关闭</el-button>
+      </div>
+    </el-dialog>
+
+    <el-dialog title="新建" :visible.sync="open" width="482px" append-to-body custom-class="version-create-dialog">
       <form class="version-form" @submit.prevent="submitForm">
         <label class="version-row required">
           <span>版本号:</span>
           <div class="version-input-line">
             <span>v</span>
-            <input v-model="form.versionNo" class="visitor-native-input" type="text" placeholder="请输入">
+            <input
+              v-model="form.versionNo"
+              class="visitor-native-input"
+              type="text"
+              inputmode="decimal"
+              pattern="[0-9]+(\.[0-9]+)*"
+              placeholder="请输入"
+              @input="handleVersionNoInput"
+            >
           </div>
         </label>
 
         <label class="version-row">
           <span>更新内容</span>
-          <textarea v-model="form.updateContent" class="visitor-native-textarea" placeholder="请输入"></textarea>
+          <textarea v-model="form.updateContent" class="visitor-native-textarea" placeholder="请输入" />
         </label>
 
         <div class="version-row required">
           <span>安装包:</span>
-          <div>
-            <button class="upload-drop" :class="{ 'has-file': packageFileName }" type="button" @click="$refs.packageFile.click()">
+          <div class="version-upload-field">
+            <button
+              class="upload-drop"
+              :class="{ 'has-file': packageFileName, 'drag-over': packageDragActive }"
+              type="button"
+              @click="$refs.packageFile.click()"
+              @dragover.prevent="handlePackageDragOver"
+              @dragleave="handlePackageDragLeave"
+              @drop.prevent="handlePackageDrop"
+            >
               <input ref="packageFile" type="file" hidden @change="handlePackageChange">
               <span><strong>{{ packageFileName ? packageFileName : '点击上传' }}</strong><i v-if="!packageFileName"> / </i>{{ packageFileName ? '' : '拖拽到此区域' }}</span>
             </button>
-            <p class="upload-tip">请上传安装包文件,大小在60M以内</p>
+            <p class="upload-tip">请上传安装包文件,大小须小于200M</p>
           </div>
         </div>
 
         <label class="version-row switch-line">
           <span>强制更新</span>
           <input v-model="form.forceUpdate" type="checkbox">
-          <span class="switch-control"></span>
+          <span class="switch-control" />
         </label>
 
         <footer class="visitor-form-actions">
@@ -78,8 +120,10 @@
 </template>
 
 <script>
-import { listVersion, addVersion, delVersion } from '@/api/visitor-admin/version'
-import { getPageRows, getPageTotal } from '@/utils/visitor-admin'
+import { listVersion, getVersion, addVersion, delVersion, uploadVersionPackage } from '@/api/visitor-admin/version'
+import { getPageRows, getPageTotal, getResponseData } from '@/utils/visitor-admin'
+
+const MAX_PACKAGE_SIZE = 200 * 1024 * 1024
 
 export default {
   name: 'VisitorVersionUpdate',
@@ -89,7 +133,11 @@
       loading: false,
       total: 0,
       open: false,
+      detailOpen: false,
+      detail: {},
+      packageFile: null,
       packageFileName: '',
+      packageDragActive: false,
       versionList: [],
       queryParams: {
         pageNum: 1,
@@ -114,6 +162,11 @@
         this.loading = false
       })
     },
+    // 目的:兼容历史版本号是否包含 v 前缀,统一按 v+版本号展示。
+    formatVersionNo(versionNo) {
+      const value = String(versionNo || '').replace(/^[vV]/, '')
+      return value ? `v${value}` : '-'
+    },
     // 目的:打开新增版本弹窗;中台只读模式下禁止本地新增。
     handleAdd() {
       if (this.isMiddlePlatformReadonly) {
@@ -121,16 +174,59 @@
         return
       }
       this.form = { forceUpdate: false, versionNo: '', updateContent: '', packageName: '', packageUrl: '' }
+      this.packageFile = null
       this.packageFileName = ''
+      this.packageDragActive = false
+      if (this.$refs.packageFile) {
+        this.$refs.packageFile.value = ''
+      }
       this.open = true
     },
-    // 目的:记录选择的安装包文件名,并回填版本包名称和地址字段。
+    // 目的:版本号只保留数字和小数点,支持多段版本号输入。
+    handleVersionNoInput(event) {
+      const versionNo = event.target.value.replace(/[^\d.]/g, '').replace(/\.{2,}/g, '.')
+      event.target.value = versionNo
+      this.form.versionNo = versionNo
+    },
+    // 目的:通过文件选择框读取安装包,并复用统一校验逻辑。
     handlePackageChange(event) {
       const file = event.target.files && event.target.files[0]
+      this.setPackageFile(file, event.target)
+    },
+    // 目的:拖拽文件经过上传区域时阻止浏览器默认行为并展示激活状态。
+    handlePackageDragOver() {
+      this.packageDragActive = true
+    },
+    // 目的:文件离开整个上传区域后恢复默认样式,避免子元素切换导致闪烁。
+    handlePackageDragLeave(event) {
+      if (!event.currentTarget.contains(event.relatedTarget)) {
+        this.packageDragActive = false
+      }
+    },
+    // 目的:接收拖放的首个文件,并复用文件选择框的校验和回填逻辑。
+    handlePackageDrop(event) {
+      this.packageDragActive = false
+      const file = event.dataTransfer.files && event.dataTransfer.files[0]
+      this.setPackageFile(file, this.$refs.packageFile)
+    },
+    // 目的:统一处理点击选择与拖拽选择的安装包,避免重复校验逻辑。
+    setPackageFile(file, input) {
+      if (file && file.size >= MAX_PACKAGE_SIZE) {
+        this.$modal.msgError('操作失败:安装包大小必须小于200M')
+        if (input) {
+          input.value = ''
+        }
+        this.packageFile = null
+        this.packageFileName = ''
+        this.form.packageName = ''
+        this.form.packageUrl = ''
+        return
+      }
+      this.packageFile = file || null
       this.packageFileName = file ? file.name : ''
       if (file) {
         this.form.packageName = file.name
-        this.form.packageUrl = file.name
+        this.form.packageUrl = ''
       }
     },
     // 目的:校验并提交新增版本;中台只读模式下禁止提交。
@@ -139,14 +235,35 @@
         this.denyMiddlePlatformWrite()
         return
       }
-      if (!this.form.versionNo || !this.form.packageUrl) {
+      if (!/^\d+(\.\d+)*$/.test(this.form.versionNo || '') || !this.packageFile) {
         this.$modal.msgError('请填写版本号并上传安装包')
         return
       }
-      addVersion(this.form).then(() => {
+      if (this.packageFile.size >= MAX_PACKAGE_SIZE) {
+        this.$modal.msgError('操作失败:安装包大小必须小于200M')
+        return
+      }
+      const formData = new FormData()
+      formData.append('file', this.packageFile)
+      uploadVersionPackage(formData).then(res => {
+        const data = getResponseData(res) || {}
+        this.form.packageUrl = data.fileName || data.url || ''
+        this.form.packageName = data.originalFilename || data.newFileName || this.packageFile.name
+        if (!this.form.packageUrl) {
+          throw new Error('安装包上传失败,未返回文件地址')
+        }
+        return addVersion(this.form)
+      }).then(() => {
         this.$modal.msgSuccess('新增成功')
         this.open = false
         this.getList()
+      })
+    },
+    // 目的:查看任意版本的完整更新信息。
+    handleView(row) {
+      getVersion(row.updateId).then(res => {
+        this.detail = getResponseData(res) || {}
+        this.detailOpen = true
       })
     },
     // 目的:删除版本更新记录;中台只读模式下禁止本地删除。
@@ -155,6 +272,10 @@
         this.denyMiddlePlatformWrite()
         return
       }
+      if (row.latest) {
+        this.$modal.msgWarning('最新版本不能删除')
+        return
+      }
       this.$modal.confirm('是否确认删除该版本记录?').then(() => delVersion([row.updateId])).then(() => {
         this.$modal.msgSuccess('删除成功')
         this.getList()

--
Gitblit v1.7.1