From 47bae1f938f915206e3934ea960aff975e5738c9 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期五, 12 六月 2026 16:09:49 +0800
Subject: [PATCH] feat(teachingDemo): 新增工艺路线与BOM教学演示模块

---
 src/views/collaborativeApproval/sealManagement/index.vue |  520 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 520 insertions(+), 0 deletions(-)

diff --git a/src/views/collaborativeApproval/sealManagement/index.vue b/src/views/collaborativeApproval/sealManagement/index.vue
new file mode 100644
index 0000000..a06f6d5
--- /dev/null
+++ b/src/views/collaborativeApproval/sealManagement/index.vue
@@ -0,0 +1,520 @@
+<template>
+  <div class="app-container">
+    <el-card class="box-card">
+      <template #header>
+        <div class="card-header">
+          <span>鐢ㄥ嵃绠$悊鍙戝竷</span>
+        </div>
+      </template>
+
+   <!-- 鐢ㄥ嵃鐢宠绠$悊 -->
+        <div class="tab-content">
+            <el-row :gutter="20" class="mb-20 ">
+              <span class="ml-10">鐢ㄥ嵃鏍囬锛�</span>
+              <el-col :span="4">
+                <el-input v-model="sealSearchForm.title" placeholder="璇疯緭鍏ョ敵璇锋爣棰�" clearable />
+              </el-col>
+              <span class="ml-10">鐢ㄥ嵃缂栧彿锛�</span>
+              <el-col :span="4">
+                <el-input v-model="sealSearchForm.applicationNum" placeholder="璇疯緭鍏ョ敤鍗扮紪鍙�" clearable />
+              </el-col>
+              <span class="search_title">瀹℃壒鐘舵�侊細</span>
+              <el-col :span="4">
+                <el-select v-model="sealSearchForm.status" placeholder="瀹℃壒鐘舵��" clearable>
+                  <el-option label="寰呭鎵�" value="pending" />
+                  <el-option label="宸查�氳繃" value="approved" />
+                  <el-option label="宸叉嫆缁�" value="rejected" />
+                </el-select>
+              </el-col>
+              <el-col :span="6">
+                <el-button type="primary" @click="searchSealApplications">鎼滅储</el-button>
+                <el-button @click="resetSealSearch">閲嶇疆</el-button>
+                <el-button @click="handleExport">瀵煎嚭</el-button>
+                <el-button type="primary" @click="showSealApplyDialog = true">鐢宠鐢ㄥ嵃
+                </el-button>
+              </el-col>
+            </el-row>
+
+            <PIMTable
+              rowKey="id"
+              :column="sealTableColumn"
+              :tableData="sealApplications"
+              :tableLoading="tableLoading"
+              :page="page"
+              :isShowPagination="true"
+              @pagination="paginationChange"
+            />
+        </div>
+    </el-card>
+
+    <!-- 鐢ㄥ嵃鐢宠瀵硅瘽妗� -->
+    <FormDialog
+      v-model="showSealApplyDialog"
+      title="鐢宠鐢ㄥ嵃"
+      :width="'600px'"
+      @close="closeSealApplyDialog"
+      @confirm="submitSealApplication"
+      @cancel="closeSealApplyDialog"
+    >
+      <el-form :model="sealForm" :rules="sealRules" ref="sealFormRef" label-width="100px">
+        <el-form-item label="鐢宠缂栧彿" prop="applicationNum">
+          <el-input v-model="sealForm.applicationNum" placeholder="璇疯緭鍏ョ敵璇风紪鍙�" />
+        </el-form-item>
+        <el-form-item label="鐢宠鏍囬" prop="title">
+          <el-input v-model="sealForm.title" placeholder="璇疯緭鍏ョ敵璇锋爣棰�" />
+        </el-form-item>
+        <el-form-item label="鐢ㄥ嵃绫诲瀷" prop="sealType">
+          <el-select v-model="sealForm.sealType" placeholder="璇烽�夋嫨鐢ㄥ嵃绫诲瀷" style="width: 100%">
+            <el-option label="鍏珷" value="official" />
+            <el-option label="鍚堝悓涓撶敤绔�" value="contract" />
+            <el-option label="璐㈠姟涓撶敤绔�" value="finance" />
+            <el-option label="娉曚汉绔�" value="legal" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="鐢宠鍘熷洜" prop="reason">
+          <el-input v-model="sealForm.reason" type="textarea" :rows="4" placeholder="璇疯缁嗚鏄庣敤鍗板師鍥�" />
+        </el-form-item>
+        <el-form-item label="瀹℃壒浜�" prop="approveUserId">
+          <el-select v-model="sealForm.approveUserId" placeholder="璇烽�夋嫨瀹℃壒浜�" style="width: 100%" filterable>
+            <el-option
+                v-for="user in userList"
+                :key="user.userId"
+                :label="user.nickName"
+                :value="user.userId"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="绱ф�ョ▼搴�" prop="urgency">
+          <el-radio-group v-model="sealForm.urgency">
+            <el-radio value="normal">鏅��</el-radio>
+            <el-radio value="urgent">绱ф��</el-radio>
+            <el-radio value="very-urgent">鐗规��</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="闄勪欢涓婁紶">
+          <AttachmentUploadFile
+            v-model:fileList="sealForm.storageBlobDTOs"
+            :limit="10"
+            :fileSize="50"
+            buttonText="鐐瑰嚮涓婁紶闄勪欢"
+          />
+        </el-form-item>
+      </el-form>
+    </FormDialog>
+
+    <!-- 鐢ㄥ嵃璇︽儏瀵硅瘽妗� -->
+    <FormDialog
+      v-model="showSealDetailDialog"
+      title="鐢ㄥ嵃鐢宠璇︽儏"
+      :width="'700px'"
+      @close="closeSealDetailDialog"
+      @confirm="closeSealDetailDialog"
+      @cancel="closeSealDetailDialog"
+    >
+      <div v-if="currentSealDetail" class="mb10">
+        <el-descriptions :column="2" border>
+          <el-descriptions-item label="鐢宠缂栧彿">{{ currentSealDetail.applicationNum }}</el-descriptions-item>
+          <el-descriptions-item label="鐢宠鏍囬">{{ currentSealDetail.title }}</el-descriptions-item>
+          <el-descriptions-item label="鐢宠浜�">{{ currentSealDetail.createUserName }}</el-descriptions-item>
+          <el-descriptions-item label="鎵�灞為儴闂�">{{ currentSealDetail.department }}</el-descriptions-item>
+          <el-descriptions-item label="鐢ㄥ嵃绫诲瀷">{{ getSealTypeText(currentSealDetail.sealType) }}</el-descriptions-item>
+          <el-descriptions-item label="鐢宠鏃堕棿">{{ currentSealDetail.createTime }}</el-descriptions-item>
+          <el-descriptions-item label="鐘舵��">
+            <el-tag :type="getStatusType(currentSealDetail.status)">
+              {{ getStatusText(currentSealDetail.status) }}
+            </el-tag>
+          </el-descriptions-item>
+          <el-descriptions-item label="鐢宠鍘熷洜" :span="2">{{ currentSealDetail.reason }}</el-descriptions-item>
+        </el-descriptions>
+        <!-- 闄勪欢鍒楄〃 -->
+        <div v-if="currentSealDetail.storageBlobVOList?.length || currentSealDetail.storageBlobDTOs?.length" class="attachment-section">
+          <div class="attachment-title">闄勪欢鍒楄〃锛�</div>
+          <el-table :data="currentSealDetail.storageBlobVOList || currentSealDetail.storageBlobDTOs" border class="attachment-table">
+            <el-table-column label="闄勪欢鍚嶇О" show-overflow-tooltip>
+              <template #default="scope">
+                {{ scope.row.originalFilename || scope.row.name || scope.row.fileName || '鏈懡鍚嶆枃浠�' }}
+              </template>
+            </el-table-column>
+            <el-table-column fixed="right" label="鎿嶄綔" width="150" align="center">
+              <template #default="scope">
+                <el-button link type="primary" size="small" @click="previewFile(scope.row)">棰勮</el-button>
+                <el-button link type="primary" size="small" @click="downloadFile(scope.row)">涓嬭浇</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+      </div>
+    </FormDialog>
+    <!-- 鏂囦欢棰勮缁勪欢 -->
+    <FilePreview ref="filePreviewRef" />
+
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, getCurrentInstance, watch } from 'vue'
+import { useRoute } from 'vue-router'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { listSealApplication, addSealApplication, updateSealApplication } from '@/api/collaborativeApproval/sealManagement.js'
+import { userListNoPageByTenantId } from '@/api/system/user.js'
+import useUserStore from '@/store/modules/user'
+import FormDialog from '@/components/Dialog/FormDialog.vue'
+import PIMTable from '@/components/PIMTable/PIMTable.vue'
+import AttachmentUploadFile from '@/components/AttachmentUpload/file/index.vue'
+import FilePreview from '@/components/filePreview/index.vue'
+import download from '@/plugins/download.js'
+
+// 鍝嶅簲寮忔暟鎹�
+// 鐢ㄥ嵃鐢宠鐩稿叧
+const userStore = useUserStore()
+const route = useRoute()
+const showSealApplyDialog = ref(false)
+const tableLoading = ref(false)
+const showSealDetailDialog = ref(false)
+const currentSealDetail = ref(null)
+const filePreviewRef = ref(null)
+const sealFormRef = ref()
+const userList = ref([])
+const sealForm = reactive({
+  id: null,
+  applicationNum: '',
+  title: '',
+  sealType: '',
+  reason: '',
+  approveUserId: '',
+  urgency: 'normal',
+  status: 'pending',
+  storageBlobDTOs: []
+})
+
+const sealRules = {
+  applicationNum: [{ required: true, message: '璇疯緭鍏ョ敵璇风紪鍙�', trigger: 'blur' }],
+  title: [{ required: true, message: '璇疯緭鍏ョ敵璇锋爣棰�', trigger: 'blur' }],
+  sealType: [{ required: true, message: '璇烽�夋嫨鐢ㄥ嵃绫诲瀷', trigger: 'change' }],
+  reason: [{ required: true, message: '璇疯緭鍏ョ敵璇峰師鍥�', trigger: 'blur' }],
+  approveUserId: [{ required: true, message: '璇烽�夋嫨瀹℃壒浜�', trigger: 'change' }]
+}
+
+const sealSearchForm = reactive({
+  title: '',
+  status: '',
+  applicationNum: ''
+})
+// 鍒嗛〉鍙傛暟
+const page = reactive({
+  current: 1,
+  size: 10,
+  total: 0
+})
+
+const sealApplications = ref([])
+
+// 鐢ㄥ嵃鐢宠鐘舵��
+const getStatusType = (status) => {
+  const statusMap = {
+    pending: 'warning',
+    approved: 'success',
+    rejected: 'danger'
+  }
+  return statusMap[status] || 'info'
+}
+// 鐢ㄥ嵃鐢宠鐘舵�佹枃鏈�
+const getStatusText = (status) => {
+  const statusMap = {
+    pending: '寰呭鎵�',
+    approved: '宸查�氳繃',
+    rejected: '宸叉嫆缁�'
+  }
+  return statusMap[status] || '鏈煡'
+}
+// 鐢ㄥ嵃绫诲瀷
+const getSealTypeText = (sealType) => {
+  const sealTypeMap = {
+    official: '鍏珷',
+    contract: '鍚堝悓涓撶敤绔�',
+    finance: '璐㈠姟涓撶敤绔�',
+    legal: '娉曚汉绔�',
+    tegal: '鎶�鏈笓鐢ㄧ珷'
+  }
+  return sealTypeMap[sealType] || '鏈煡'
+}
+
+// 鐢ㄥ嵃鐢宠琛ㄦ牸鍒楅厤缃紙闇�鍦� getStatusText/getSealTypeText 绛変箣鍚庡畾涔夛級
+const sealTableColumn = ref([
+  { label: '鐢宠缂栧彿', prop: 'applicationNum' },
+  { label: '鐢宠鏍囬', prop: 'title', showOverflowTooltip: true },
+  { label: '鐢宠浜�', prop: 'createUserName' },
+  { label: '鎵�灞為儴闂�', prop: 'department', width: 150 },
+  {
+    label: '鐢ㄥ嵃绫诲瀷',
+    prop: 'sealType',
+    dataType: 'tag',
+    formatData: (v) => getSealTypeText(v),
+    formatType: () => 'info'
+  },
+  { label: '鐢宠鏃堕棿', prop: 'createTime', width: 180 },
+  {
+    label: '鐘舵��',
+    prop: 'status',
+    width: 100,
+    dataType: 'tag',
+    formatData: (v) => getStatusText(v),
+    formatType: (v) => getStatusType(v)
+  },
+  { label: '瀹℃壒浜�', prop: 'approveUserName', width: 100 },
+  {
+    dataType: 'action',
+    label: '鎿嶄綔',
+    width: 250,
+    fixed: 'right',
+    align: 'center',
+    operation: [
+      {
+        name: '瀹℃壒',
+        clickFun: (row) => approveSeal(row),
+        showHide: (row) => row.status === 'pending' && Number(userStore.id) === row.approveUserId
+      },
+      {
+        name: '鎷掔粷',
+        clickFun: (row) => rejectSeal(row),
+        showHide: (row) => row.status === 'pending' && Number(userStore.id) === row.approveUserId
+      },
+      {
+        name: '閲嶆柊鐢宠',
+        clickFun: (row) => reapplySeal(row),
+        showHide: (row) => row.status === 'rejected' && Number(userStore.id) === row.createUser
+      },
+      { name: '璇︽儏', clickFun: (row) => viewSealDetail(row) }
+    ]
+  }
+])
+
+// 鎼滅储鍗扮珷鐢宠
+const searchSealApplications = () => {
+  page.current = 1
+  getSealApplicationList()
+}
+// 閲嶇疆鍗扮珷鐢宠鎼滅储
+const resetSealSearch = () => {
+  sealSearchForm.title = ''
+  sealSearchForm.status = ''
+  sealSearchForm.applicationNum = ''
+  searchSealApplications()
+}
+
+// 閲嶆柊鐢宠鐢ㄥ嵃
+const reapplySeal = (row) => {
+  // 棰勫~琛ㄥ崟鏁版嵁
+  Object.assign(sealForm, {
+    id: row.id,
+    applicationNum: row.applicationNum,
+    title: row.title,
+    sealType: row.sealType,
+    reason: row.reason,
+    approveUserId: row.approveUserId,
+    urgency: row.urgency || 'normal',
+    status: 'pending',
+    storageBlobDTOs: row.storageBlobVOList || []
+  })
+  showSealApplyDialog.value = true
+}
+
+// 鎻愪氦鐢ㄥ嵃鐢宠
+const submitSealApplication = async () => {
+  try {
+    await sealFormRef.value.validate()
+    const request = sealForm.id ? updateSealApplication : addSealApplication
+    request(sealForm).then(res => {
+      if (res.code == 200) {
+        ElMessage.success(sealForm.id ? '閲嶆柊鐢宠鎴愬姛' : '鐢宠鎻愪氦鎴愬姛')
+        closeSealApplyDialog()
+        getSealApplicationList()
+        Object.assign(sealForm, {
+          id: null,
+          applicationNum: '',
+          title: '',
+          sealType: '',
+          reason: '',
+          approveUserId: '',
+          urgency: 'normal',
+          status: 'pending',
+          storageBlobDTOs: []
+        })
+      }
+    }).catch(err => {
+      console.log(err.msg)
+    })
+  } catch (error) {
+  }
+}
+
+// 鍏抽棴鐢ㄥ嵃鐢宠瀵硅瘽妗�
+const closeSealApplyDialog = () => {
+  // 娓呯┖琛ㄥ崟鏁版嵁
+  Object.assign(sealForm, {
+    id: null,
+    applicationNum: '',
+    title: '',
+    sealType: '',
+    reason: '',
+    approveUserId: '',
+    urgency: 'normal',
+    status: 'pending',
+    storageBlobDTOs: []
+  })
+  // 娓呴櫎琛ㄥ崟楠岃瘉鐘舵��
+  if (sealFormRef.value) {
+    sealFormRef.value.clearValidate()
+  }
+  showSealApplyDialog.value = false
+}
+// 鍏抽棴鐢ㄥ嵃璇︽儏瀵硅瘽妗�
+const closeSealDetailDialog = () => {
+  showSealDetailDialog.value = false
+}
+
+// 鏌ョ湅鐢ㄥ嵃鐢宠璇︽儏
+const viewSealDetail = (row) => {
+  currentSealDetail.value = row
+  showSealDetailDialog.value = true
+}
+
+// 棰勮鏂囦欢
+const previewFile = (row) => {
+  const url = row.previewURL || row.previewUrl || row.url
+  if (url && filePreviewRef.value) {
+    filePreviewRef.value.open(url)
+  } else {
+    ElMessage.warning('鏂囦欢鍦板潃鏃犳晥锛屾棤娉曢瑙�')
+  }
+}
+
+// 涓嬭浇鏂囦欢
+const downloadFile = (row) => {
+  const url = row.downloadURL || row.downloadUrl || row.url
+  if (url) {
+    const filename = row.originalFilename || row.name || row.fileName || 'download'
+    download.byUrl(url, filename)
+  } else {
+    ElMessage.warning('鏂囦欢鍦板潃鏃犳晥锛屾棤娉曚笅杞�')
+  }
+}
+// 瀹℃壒鐢ㄥ嵃鐢宠
+const approveSeal = (row) => {
+  ElMessageBox.confirm('纭閫氳繃璇ョ敤鍗扮敵璇凤紵', '鎻愮ず', {
+    confirmButtonText: '纭畾',
+    cancelButtonText: '鍙栨秷',
+    type: 'warning'
+  }).then(() => {
+    row.status = 'approved'
+    updateSealApplication(row).then(res => {
+      if (res.code == 200) {
+        ElMessage.success('瀹℃壒閫氳繃')
+        getSealApplicationList()
+      }
+    })
+  })
+}
+// 鎷掔粷鐢ㄥ嵃鐢宠
+const rejectSeal = (row) => {
+  ElMessageBox.prompt('璇疯緭鍏ユ嫆缁濆師鍥�', '鎻愮ず', {
+    confirmButtonText: '纭畾',
+    cancelButtonText: '鍙栨秷',
+    inputPattern: /\S+/,
+    inputErrorMessage: '鎷掔粷鍘熷洜涓嶈兘涓虹┖'
+  }).then(({ value }) => {
+    row.status = 'rejected'
+    row.reason = value
+    updateSealApplication(row).then(res => {
+      if (res.code == 200) {
+        ElMessage.success('宸叉嫆缁濈敵璇�')
+        getSealApplicationList()
+      }
+    })
+  })
+}
+
+// 瀵煎嚭鐢ㄥ嵃鐢宠
+const { proxy } = getCurrentInstance()
+const handleExport = () => {
+  proxy.download('/sealApplicationManagement/export', { ...sealSearchForm }, '鐢ㄥ嵃鐢宠.xlsx')
+}
+
+// 鑾峰彇鍗扮珷鐢宠鍒楄〃鏁版嵁
+const getSealApplicationList = async () => {
+  tableLoading.value = true
+  listSealApplication(page, sealSearchForm)
+    .then(res => {
+      sealApplications.value = res.data.records
+      page.total = res.data.total
+      tableLoading.value = false
+    }).catch(err => {
+      tableLoading.value = false
+    })
+}
+// 鍒嗛〉鍙樺寲澶勭悊
+const paginationChange = (obj) => {
+  page.current = obj.page;
+  page.size = obj.limit;
+  getSealApplicationList();
+};
+
+// 鐩戝惉瀵硅瘽妗嗘墦寮�锛岃幏鍙栫敤鎴峰垪琛�
+watch(showSealApplyDialog, (newVal) => {
+  if (newVal) {
+    userListNoPageByTenantId().then((res) => {
+      userList.value = res.data;
+    });
+  }
+});
+
+onMounted(() => {
+  // 璺敱鎼哄甫 applicationNum 鏃讹紝棰勫~骞舵煡璇�
+  if (route.query.applicationNum) {
+    sealSearchForm.applicationNum = String(route.query.applicationNum)
+    page.current = 1
+    getSealApplicationList()
+  } else {
+    getSealApplicationList()
+  }
+})
+</script>
+
+<style scoped>
+.app-container {
+  padding: 20px;
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.tab-content {
+  padding: 20px 0;
+}
+
+.mb-20 {
+  margin-bottom: 20px;
+}
+
+.ml-10 {
+  margin-left: 10px;
+}
+
+.attachment-section {
+  margin-top: 20px;
+}
+
+.attachment-title {
+  font-size: 14px;
+  color: #606266;
+  margin-bottom: 10px;
+  font-weight: 500;
+}
+
+.attachment-table {
+  border-radius: 4px;
+}
+</style>
\ No newline at end of file

--
Gitblit v1.9.3