From 04b1a9cfde4049be9a38b9832d5289d4a192c883 Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期五, 15 五月 2026 16:29:33 +0800
Subject: [PATCH] 加班申请模块和审批流程公共组件

---
 src/views/system/appVersion/index.vue |  268 +++++++++++++++++++++++++++++++---------------------
 1 files changed, 159 insertions(+), 109 deletions(-)

diff --git a/src/views/system/appVersion/index.vue b/src/views/system/appVersion/index.vue
index 6d68c07..6ff9ae6 100644
--- a/src/views/system/appVersion/index.vue
+++ b/src/views/system/appVersion/index.vue
@@ -7,9 +7,9 @@
     </el-row>
 
     <el-table v-loading="loading" :data="versionList">
-      <el-table-column label="ID" prop="id" align="center" width="80" />
-      <el-table-column label="搴旂敤鍚嶇О" prop="name" align="center" min-width="150" />
-      <el-table-column label="鐗堟湰鍙�" prop="version" align="center" width="120" />
+      <el-table-column label="ID" prop="id" align="center" width="80"/>
+      <el-table-column label="搴旂敤鍚嶇О" prop="name" align="center" min-width="150"/>
+      <el-table-column label="鐗堟湰鍙�" prop="version" align="center" width="120"/>
       <el-table-column label="鍒涘缓鏃堕棿" prop="createTime" align="center" width="170">
         <template #default="scope">
           <span>{{ parseTime(scope.row.createTime, "{y}-{m}-{d} {h}:{i}:{s}") }}</span>
@@ -20,46 +20,34 @@
           <span>{{ parseTime(scope.row.updateTime, "{y}-{m}-{d} {h}:{i}:{s}") }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="鍒涘缓浜�" prop="createUser" align="center" width="100" />
-      <el-table-column label="鎿嶄綔" align="center" width="100" class-name="small-padding fixed-width">
+      <el-table-column label="鍒涘缓浜�" prop="createUser" align="center" width="100"/>
+      <el-table-column label="鎿嶄綔" align="center" width="180" class-name="small-padding fixed-width">
         <template #default="scope">
           <el-button link type="primary" @click="downloadAttachment(scope.row)">涓嬭浇</el-button>
+          <el-button link type="success" @click="openQrDialog(scope.row)">鎵爜涓嬭浇</el-button>
         </template>
       </el-table-column>
     </el-table>
 
     <pagination
-      v-show="total > 0"
-      :total="total"
-      v-model:page="queryParams.current"
-      v-model:limit="queryParams.size"
-      @pagination="getList"
+        v-show="total > 0"
+        :total="total"
+        v-model:page="queryParams.current"
+        v-model:limit="queryParams.size"
+        @pagination="getList"
     />
 
     <el-dialog title="涓婁紶APK" v-model="uploadOpen" width="560px" append-to-body @close="resetUploadForm">
       <el-form ref="uploadRef" :model="uploadForm" :rules="uploadRules" label-width="90px">
         <el-form-item label="搴旂敤鍚嶇О" prop="name">
-          <el-input v-model="uploadForm.name" placeholder="璇疯緭鍏ュ簲鐢ㄥ悕绉�" />
+          <el-input v-model="uploadForm.name" placeholder="璇疯緭鍏ュ簲鐢ㄥ悕绉�"/>
         </el-form-item>
         <el-form-item label="鐗堟湰鍙�" prop="version">
-          <el-input v-model="uploadForm.version" placeholder="璇疯緭鍏ョ増鏈彿" />
+          <el-input v-model="uploadForm.version" placeholder="璇疯緭鍏ョ増鏈彿"/>
         </el-form-item>
-        <el-form-item label="APK鏂囦欢" prop="file">
-          <el-upload
-            :auto-upload="false"
-            :show-file-list="true"
-            :limit="1"
-            accept=".apk"
-            :on-change="handleApkChange"
-            :on-remove="handleApkRemove"
-            :on-exceed="handleApkExceed"
-            :before-upload="beforeApkUpload"
-          >
-            <el-button type="primary">閫夋嫨APK鏂囦欢</el-button>
-            <template #tip>
-              <div class="el-upload__tip">鍙兘涓婁紶 APK 鏂囦欢锛屼笖涓嶈秴杩� 200MB</div>
-            </template>
-          </el-upload>
+        <el-form-item label="APK鏂囦欢" prop="storageBlobDTOList">
+          <FileUpload v-model:file-list="uploadForm.storageBlobDTOList" :limit="1" :file-type="['apk']"
+                      :file-size="200"/>
         </el-form-item>
       </el-form>
       <template #footer>
@@ -69,13 +57,45 @@
         </div>
       </template>
     </el-dialog>
+
+    <el-dialog
+      v-model="qrOpen"
+      title="鎵爜涓嬭浇"
+      width="460px"
+      append-to-body
+      class="download-qr-dialog"
+    >
+      <div class="download-qr-content">
+        <div class="app-meta-card">
+          <div class="meta-row">
+            <span class="meta-label">搴旂敤鍚嶇О</span>
+            <span class="meta-value">{{ qrCurrentRow?.name || "-" }}</span>
+          </div>
+          <div class="meta-row">
+            <span class="meta-label">鐗堟湰缂栧彿</span>
+            <span class="meta-value">{{ qrCurrentRow?.version || "-" }}</span>
+          </div>
+        </div>
+        <div class="qr-box">
+          <img v-if="qrCodeUrl" :src="qrCodeUrl" alt="download qr code" class="qr-image" />
+          <div class="qr-tip">璇蜂娇鐢ㄦ墜鏈烘壂鐮佷笅杞藉簲鐢�</div>
+        </div>
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="qrOpen = false">鍏� 闂�</el-button>
+        </div>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup name="SystemAppVersion">
-import { listAppVersion, uploadApk } from "@/api/system/appVersion";
+import {listAppVersion, add} from "@/api/system/appVersion";
+import FileUpload from "@/components/AttachmentUpload/file/index.vue";
+import QRCode from "qrcode";
 
-const { proxy } = getCurrentInstance();
+const {proxy} = getCurrentInstance();
 
 const loading = ref(false);
 const versionList = ref([]);
@@ -88,16 +108,19 @@
 
 const uploadOpen = ref(false);
 const uploading = ref(false);
+const qrOpen = ref(false);
+const qrCodeUrl = ref("");
+const qrCurrentRow = ref(null);
 const uploadForm = reactive({
   name: "",
   version: "",
-  file: null,
+  storageBlobDTOList: null,
 });
 
 const uploadRules = {
-  name: [{ required: true, message: "璇疯緭鍏ュ簲鐢ㄥ悕绉�", trigger: "blur" }],
-  version: [{ required: true, message: "璇疯緭鍏ョ増鏈彿", trigger: "blur" }],
-  file: [{ required: true, message: "璇蜂笂浼燗PK鏂囦欢", trigger: "change" }],
+  name: [{required: true, message: "璇疯緭鍏ュ簲鐢ㄥ悕绉�", trigger: "blur"}],
+  version: [{required: true, message: "璇疯緭鍏ョ増鏈彿", trigger: "blur"}],
+  storageBlobDTOList: [{required: true, message: "璇蜂笂浼燗PK鏂囦欢", trigger: "change"}],
 };
 
 function normalizeListResp(res) {
@@ -113,14 +136,14 @@
 function getList() {
   loading.value = true;
   listAppVersion(queryParams)
-    .then(res => {
-      const result = normalizeListResp(res);
-      versionList.value = result.records;
-      total.value = result.total;
-    })
-    .finally(() => {
-      loading.value = false;
-    });
+      .then(res => {
+        const result = normalizeListResp(res);
+        versionList.value = result.records;
+        total.value = result.total;
+      })
+      .finally(() => {
+        loading.value = false;
+      });
 }
 
 function openUploadDialog() {
@@ -131,86 +154,50 @@
 function resetUploadForm() {
   uploadForm.name = "";
   uploadForm.version = "";
-  uploadForm.file = null;
+  uploadForm.storageBlobDTOList = null;
   proxy.resetForm("uploadRef");
 }
 
-function beforeApkUpload(file) {
-  const isApk = /\.apk$/i.test(file.name);
-  if (!isApk) {
-    proxy.$modal.msgWarning("鍙兘涓婁紶APK鏂囦欢");
-    return false;
-  }
-  const isLt200M = file.size / 1024 / 1024 < 200;
-  if (!isLt200M) {
-    proxy.$modal.msgWarning("APK 鏂囦欢澶у皬涓嶈兘瓒呰繃 200MB");
-    return false;
-  }
-  return true;
-}
-
-function handleApkChange(file) {
-  if (!file || !file.raw) return;
-  if (!beforeApkUpload(file.raw)) {
-    uploadForm.file = null;
-    proxy.$refs.uploadRef?.clearValidate("file");
-    return;
-  }
-  uploadForm.file = file.raw;
-}
-
-function handleApkRemove() {
-  uploadForm.file = null;
-}
-
-function handleApkExceed() {
-  proxy.$modal.msgWarning("鍙兘涓婁紶涓�涓狝PK鏂囦欢");
-}
 
 function downloadAttachment(row) {
-  const filePath =
-    (row.commonFileList &&
-      row.commonFileList.length > 0 &&
-      row.commonFileList[0].url) ||
-    row.url;
-  if (!filePath) {
-    proxy.$modal.msgError("涓嬭浇閾炬帴涓嶅瓨鍦�");
+  window.open(row.downloadURL, "_blank");
+}
+
+async function openQrDialog(row) {
+  if (!row?.downloadURL) {
+    proxy.$modal.msgError("褰撳墠璁板綍缂哄皯涓嬭浇鍦板潃锛屾棤娉曠敓鎴愪簩缁寸爜");
     return;
   }
-
-  const link = document.createElement("a");
-  const rawName = String(row.name || "").trim();
-  const fallbackName = String(filePath).split("/").pop()?.split("?")[0] || "app";
-  const baseName = rawName || fallbackName;
-  const downloadName = /\.apk$/i.test(baseName) ? baseName : `${baseName}.apk`;
-  console.log(downloadName,filePath,'downloadName,filePath');
-  link.href = filePath;
-  link.download = downloadName;
-  link.target = "_blank";
-  link.rel = "noopener noreferrer";
-  document.body.appendChild(link);
-  link.click();
-  document.body.removeChild(link);
+  try {
+    qrCodeUrl.value = await QRCode.toDataURL(row.downloadURL, {
+      width: 220,
+      margin: 2,
+      errorCorrectionLevel: "M",
+      color: {
+        dark: "#1f2937",
+        light: "#ffffff",
+      },
+    });
+    qrCurrentRow.value = row;
+    qrOpen.value = true;
+  } catch (error) {
+    proxy.$modal.msgError("浜岀淮鐮佺敓鎴愬け璐ワ紝璇风◢鍚庨噸璇�");
+  }
 }
 
 function submitUpload() {
   proxy.$refs.uploadRef.validate(valid => {
     if (!valid) return;
-    const formData = new FormData();
-    formData.append("name", uploadForm.name);
-    formData.append("version", uploadForm.version);
-    formData.append("file", uploadForm.file);
-
     uploading.value = true;
-    uploadApk(formData)
-      .then(() => {
-        proxy.$modal.msgSuccess("涓婁紶鎴愬姛");
-        uploadOpen.value = false;
-        getList();
-      })
-      .finally(() => {
-        uploading.value = false;
-      });
+    add(uploadForm)
+        .then(() => {
+          proxy.$modal.msgSuccess("涓婁紶鎴愬姛");
+          uploadOpen.value = false;
+          getList();
+        })
+        .finally(() => {
+          uploading.value = false;
+        });
   });
 }
 
@@ -218,3 +205,66 @@
   getList();
 });
 </script>
+
+<style scoped>
+.download-qr-content {
+  padding: 4px 4px 8px;
+}
+
+.app-meta-card {
+  border-radius: 10px;
+  padding: 12px 14px;
+  margin-bottom: 16px;
+  background: linear-gradient(135deg, #f0f7ff 0%, #ecfdf5 100%);
+  border: 1px solid #dbeafe;
+}
+
+.meta-row {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  line-height: 26px;
+}
+
+.meta-row + .meta-row {
+  margin-top: 6px;
+}
+
+.meta-label {
+  font-size: 13px;
+  color: #64748b;
+}
+
+.meta-value {
+  max-width: 260px;
+  font-size: 14px;
+  color: #0f172a;
+  font-weight: 600;
+  word-break: break-all;
+  text-align: right;
+}
+
+.qr-box {
+  border: 1px solid #e2e8f0;
+  border-radius: 12px;
+  padding: 18px 12px 14px;
+  text-align: center;
+  background: #ffffff;
+  box-shadow: 0 6px 20px rgba(15, 23, 42, 0.06);
+}
+
+.qr-image {
+  width: 220px;
+  height: 220px;
+  border-radius: 8px;
+  border: 1px solid #e5e7eb;
+  padding: 8px;
+  background: #fff;
+}
+
+.qr-tip {
+  margin-top: 10px;
+  font-size: 13px;
+  color: #64748b;
+}
+</style>

--
Gitblit v1.9.3