spring
14 小时以前 ebe3507d1382bb124c1fb895a5e27f7063fdffdc
fix: 规章制度加一个附件功能
已添加1个文件
已修改2个文件
517 ■■■■ 文件已修改
src/api/collaborativeApproval/rulesRegulationsManagementFile.js 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Dialog/FileListDialog.vue 152 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/rulesRegulationsManagement/index.vue 337 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/collaborativeApproval/rulesRegulationsManagementFile.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
import request from "@/utils/request";
// é™„件列表
export function listRuleFiles(query) {
  return request({
    url: "/rulesRegulationsManagementFile/listPage",
    method: "get",
    params: query,
  });
}
// æ–°å¢žé™„ä»¶
export function addRuleFile(data) {
  return request({
    url: "/rulesRegulationsManagementFile/add",
    method: "post",
    data,
  });
}
// åˆ é™¤é™„件(支持传递 id æ•°ç»„)
export function delRuleFile(ids) {
  return request({
    url: "/rulesRegulationsManagementFile/del",
    method: "delete",
    data: ids,
  });
}
src/components/Dialog/FileListDialog.vue
@@ -5,6 +5,37 @@
    :width="width" 
    :before-close="handleClose"
  >
    <div class="file-list-toolbar" v-if="showToolbar">
      <template v-if="useBuiltInUpload">
        <el-upload
          v-model:file-list="uploadFileList"
          class="upload-demo"
          :action="uploadAction"
          :headers="uploadHeaders"
          :show-file-list="false"
          :on-success="handleDefaultUploadSuccess"
          :on-error="handleDefaultUploadError"
        >
          <el-button
            v-if="showUploadButton"
            type="primary"
            size="small"
          >
            ä¸Šä¼ é™„ä»¶
          </el-button>
        </el-upload>
      </template>
      <template v-else>
        <el-button
          v-if="showUploadButton"
          type="primary"
          size="small"
          @click="handleUpload"
        >
          æ–°å¢žé™„ä»¶
        </el-button>
      </template>
    </div>
    <el-table :data="tableData" border :height="tableHeight">
      <el-table-column 
        :label="nameColumnLabel" 
@@ -38,6 +69,15 @@
          >
            é¢„览
          </el-button>
          <el-button
            v-if="showDeleteButton"
            link
            type="danger"
            size="small"
            @click="handleDelete(scope.row, scope.$index)"
          >
            åˆ é™¤
          </el-button>
          <slot name="actions" :row="scope.row"></slot>
        </template>
      </el-table-column>
@@ -49,7 +89,9 @@
<script setup>
import { ref, computed, getCurrentInstance } from 'vue'
import { ElMessage } from 'element-plus'
import filePreview from '@/components/filePreview/index.vue'
import { getToken } from '@/utils/auth'
const props = defineProps({
  modelValue: {
@@ -82,7 +124,7 @@
  },
  actionColumnWidth: {
    type: [String, Number],
    default: 100
    default: 160
  },
  showActions: {
    type: Boolean,
@@ -96,6 +138,14 @@
    type: Boolean,
    default: true
  },
  showUploadButton: {
    type: Boolean,
    default: false
  },
  showDeleteButton: {
    type: Boolean,
    default: false
  },
  urlField: {
    type: String,
    default: 'url'
@@ -107,13 +157,30 @@
  previewMethod: {
    type: Function,
    default: null
  },
  uploadMethod: {
    type: Function,
    default: null
  },
  deleteMethod: {
    type: Function,
    default: null
  },
  rulesRegulationsManagementId: {
    type: [String, Number],
    default: ''
  },
  uploadUrl: {
    type: String,
    default: `${import.meta.env.VITE_APP_BASE_API}/file/upload`
  }
})
const emit = defineEmits(['update:modelValue', 'close', 'download', 'preview'])
const emit = defineEmits(['update:modelValue', 'close', 'download', 'preview', 'upload', 'delete'])
const { proxy } = getCurrentInstance()
const filePreviewRef = ref(null)
const uploadFileList = ref([])
const dialogVisible = computed({
  get: () => props.modelValue,
@@ -121,6 +188,12 @@
})
const tableData = ref([])
const showToolbar = computed(() => props.showUploadButton)
const useBuiltInUpload = computed(() => !props.uploadMethod)
const uploadAction = computed(() => props.uploadUrl)
const uploadHeaders = computed(() => ({
  Authorization: `Bearer ${getToken()}`
}))
const handleClose = () => {
  emit('close')
@@ -154,11 +227,84 @@
  tableData.value = list || []
}
const handleUpload = async () => {
  if (props.uploadMethod) {
    const newItem = await props.uploadMethod()
    if (newItem) {
      addAttachment(newItem)
    }
  }
  emit('upload')
}
const handleDelete = async (row, index) => {
  if (props.deleteMethod) {
    const result = await props.deleteMethod(row, index)
    if (result === false) {
      return
    }
    // å¦‚果提供了 deleteMethod,由父组件负责刷新列表,不在这里删除
  } else {
    // å¦‚果没有提供 deleteMethod,才在组件内部删除
    removeAttachment(index)
  }
  emit('delete', row)
}
const addAttachment = (item) => {
  tableData.value = [...tableData.value, item]
}
const handleDefaultUploadSuccess = async (res, file) => {
  if (res?.code !== 200) {
    ElMessage.error(res?.msg || '文件上传失败')
    return
  }
  if (!props.rulesRegulationsManagementId) {
    ElMessage.error('缺少规章制度ID,无法保存附件')
    return
  }
  const fileName = res?.data?.originalName || file?.name
  const fileUrl = res?.data?.tempPath || res?.data?.url
  const payload = {
    fileName,
    fileUrl,
    rulesRegulationsManagementId: props.rulesRegulationsManagementId,
    raw: res?.data || {}
  }
  emit('upload', payload)
}
const handleDefaultUploadError = () => {
  ElMessage.error('文件上传失败')
}
const removeAttachment = (index) => {
  if (index > -1 && index < tableData.value.length) {
    const newList = [...tableData.value]
    newList.splice(index, 1)
    tableData.value = newList
  }
}
const setList = (list) => {
  tableData.value = list || []
}
defineExpose({
  open
  open,
  addAttachment,
  removeAttachment,
  setList,
  handleUpload,
  handleDelete
})
</script>
<style scoped>
.file-list-toolbar {
  margin-bottom: 8px;
  text-align: right;
}
</style>
src/views/collaborativeApproval/rulesRegulationsManagement/index.vue
@@ -52,54 +52,21 @@
                  </template>
                </el-table-column>
                <el-table-column prop="readCount" label="已读人数" width="100" />
                <el-table-column label="操作" width="250" fixed="right">
                <el-table-column label="操作" width="320" fixed="right">
                  <template #default="scope">
                    <el-button link @click="viewRegulation(scope.row)">查看</el-button>
                    <el-button link type="primary" @click="handleEdit(scope.row)">编辑</el-button>
                    <el-button link type="danger" @click="repealEdit(scope.row)">废弃</el-button>
                    <el-button link type="success" @click="viewVersionHistory(scope.row)">版本历史</el-button>
                    <el-button link type="warning" @click="viewReadStatus(scope.row)">阅读状态</el-button>
                    <el-button link type="primary" @click="openFileDialog(scope.row)">附件</el-button>
                  </template>
                </el-table-column>
              </el-table>
            </div>
          </el-card>
    <!-- ç”¨å°ç”³è¯·å¯¹è¯æ¡† -->
    <!-- <el-dialog v-model="showSealApplyDialog" title="申请用印" width="600px">
      <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="urgency">
          <el-radio-group v-model="sealForm.urgency">
            <el-radio label="normal">普通</el-radio>
            <el-radio label="urgent">紧急</el-radio>
            <el-radio label="very-urgent">特急</el-radio>
          </el-radio-group>
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="showSealApplyDialog = false">取消</el-button>
          <el-button type="primary" @click="submitSealApplication">提交申请</el-button>
        </span>
      </template>
    </el-dialog> -->
    <!-- ç”¨å°ç”³è¯·å¯¹è¯æ¡†ï¼ˆå·²ç§»é™¤ï¼‰ -->
    <!-- è§„章制度发布对话框 -->
    <el-dialog v-model="showRegulationDialog" :title="operationType === 'add' ? '发布制度' : '编辑制度'" width="800px">
@@ -150,25 +117,7 @@
      </template>
    </el-dialog>
    <!-- ç”¨å°è¯¦æƒ…对话框 -->
    <!-- <el-dialog v-model="showSealDetailDialog" title="用印申请详情" width="700px">
      <div v-if="currentSealDetail" class="mb10">
        <el-descriptions :column="2" border>
          <el-descriptions-item label="申请编号">{{ currentSealDetail.id }}</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>
    </el-dialog> -->
    <!-- ç”¨å°è¯¦æƒ…对话框(已移除) -->
    <!-- è§„章制度详情对话框 -->
    <el-dialog v-model="showRegulationDetailDialog" title="规章制度详情" width="800px">
@@ -224,54 +173,42 @@
        </el-table-column>
      </el-table>
    </el-dialog>
    <FileListDialog
      ref="fileListDialogRef"
      v-model="fileDialogVisible"
      :show-upload-button="true"
      :show-delete-button="true"
      :delete-method="handleAttachmentDelete"
      :rules-regulations-management-id="currentFileRuleId"
      :name-column-label="'附件名称'"
      @upload="handleAttachmentUpload"
    />
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, getCurrentInstance } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import { listSealApplication, addSealApplication, updateSealApplication,listRuleManagement,addRuleManagement,updateRuleManagement,delRuleManagement,getReadingStatusByRuleId,getReadingStatusList,addReadingStatus,updateReadingStatus  } from '@/api/collaborativeApproval/sealManagement.js'
import { el } from 'element-plus/es/locales.mjs'
import { getUserProfile } from '@/api/system/user.js'
import {staffJoinDel, staffJoinListPage} from "@/api/personnelManagement/onboarding.js";
import useUserStore from '@/store/modules/user'
import { userLoginFacotryList } from "@/api/system/user.js"
import { listRuleManagement,addRuleManagement,updateRuleManagement,delRuleManagement,getReadingStatusByRuleId,addReadingStatus,updateReadingStatus  } from '@/api/collaborativeApproval/sealManagement.js'
import FileListDialog from '@/components/Dialog/FileListDialog.vue'
import { listRuleFiles, delRuleFile, addRuleFile } from '@/api/collaborativeApproval/rulesRegulationsManagementFile.js'
// å“åº”式数据
const currentUser = ref(null)
const activeTab = ref('seal')
const operationType = ref('add')
const tableData = ref([])
// ç”¨å°ç”³è¯·ç›¸å…³
const userStore = useUserStore()
const showSealApplyDialog = ref(false)
const tableLoading = ref(false)
const showSealDetailDialog = ref(false)
const currentSealDetail = ref(null)
const sealFormRef = ref()
const sealForm = reactive({
  applicationNum: '',
  title: '',
  sealType: '',
  reason: '',
  urgency: 'normal',
  status: 'pending'
})
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' }]
}
const sealSearchForm = reactive({
  title: '',
  status: ''
})
// åˆ†é¡µå‚æ•°
const page = reactive({
  current: 1,
  size: 10,
  total: 0
})
// é™„件弹窗
const fileDialogVisible = ref(false)
const fileListDialogRef = ref(null)
const currentFileRuleId = ref(null)
const filePage = reactive({
  current: 1,
  size: 10,
  total: 0
@@ -320,9 +257,6 @@
  category: ''
})
// å‡æ•°æ®
const sealApplications = ref([])
const regulations = ref([])
const versionHistory = ref([])
@@ -332,34 +266,6 @@
  // { employee: '刘雅婷', department: '技术部', readTime: '2025-01-11 14:20:00', confirmTime: '', status: 'unconfirmed' },
  // { employee: '王建国', department: '财务部', readTime: '2025-01-12 09:15:00', confirmTime: '2025-01-12 09:20:00', status: 'confirmed' }
// ç”¨å°ç”³è¯·çŠ¶æ€
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: '财务专用章',
    tegal: '技术专用章'
  }
  return sealTypeMap[sealType] || '未知'
}
// åˆ¶åº¦åˆ†ç±»
const getCategoryText = (category) => {
  const categoryMap = {
@@ -369,19 +275,6 @@
    tech: '技术制度'
  }
  return categoryMap[category] || '未知'
}
// æœç´¢å°ç« ç”³è¯·
const searchSealApplications = () => {
  page.current=1
  getSealApplicationList()
  // ElMessage.success('搜索完成')
}
// é‡ç½®å°ç« ç”³è¯·æœç´¢
const resetSealSearch = () => {
  sealSearchForm.title = ''
  sealSearchForm.status = ''
  searchSealApplications()
}
// æœç´¢åˆ¶åº¦
const searchRegulations = () => {
@@ -393,32 +286,6 @@
  regulationSearchForm.title = ''
  regulationSearchForm.category = ''
  searchRegulations()
}
// æäº¤ç”¨å°ç”³è¯·
const submitSealApplication = async () => {
  try {
    await sealFormRef.value.validate()
    addSealApplication(sealForm).then(res => {
      if(res.code == 200){
        ElMessage.success('申请提交成功')
        showSealApplyDialog.value = false
        getSealApplicationList()
        Object.assign(sealForm, {
        applicationNum: '',
        title: '',
        sealType: '',
        reason: '',
        urgency: 'normal',
        status: 'pending'
      })
      }
    }).catch(err => {
      ElMessage.error(err.msg)
    })
  } catch (error) {
    ElMessage.error('请完善申请信息')
  }
}
// æ–°å¢ž
const handleAdd = () => {
@@ -501,72 +368,6 @@
}
// æŸ¥çœ‹ç”¨å°ç”³è¯·è¯¦æƒ…
const viewSealDetail = (row) => {
  currentSealDetail.value = row
  showSealDetailDialog.value = true
}
// å®¡æ‰¹ç”¨å°ç”³è¯·
const approveSeal = (row) => {
  console.log(row)
  ElMessageBox.confirm('确认通过该用印申请?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    row.status = 'approved'
    updateSealApplication(row).then(res => {
      if(res.code == 200){
        ElMessage.success('审批通过')
      }
    })
  })
}
// æ‹’绝用印申请
const rejectSeal = (row) => {
  ElMessageBox.prompt('请输入拒绝原因', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    inputPattern: /\S+/,
    inputErrorMessage: '拒绝原因不能为空'
  }).then(({ value }) => {
    row.status = 'rejected'
    updateSealApplication(row).then(res => {
      if(res.code == 200){
        ElMessage.success('审批拒绝')
      }
    })
    ElMessage.success('已拒绝申请')
  })
}
// èŽ·å–åœ¨èŒå‘˜å·¥åˆ—è¡¨
const getList = () => {
  tableLoading.value = true;
      //获取当前登录用户信息
  getUserProfile().then(res => {
    if(res.code == 200){
      console.log(res.data.userName)
      currentUser.value = res.data.userName
    }
  })
  staffJoinListPage({staffState: 1}).then(res => {
    tableLoading.value = false;
    // tableData.value = res.data.records
    // //筛选出和currentUser同名的人员
    tableData.value = res.data.records.filter(item => item.staffName === currentUser.value)
    console.log("tableData",tableData.value)
    page.total = res.data.total;
    if(tableData.value.length == 0){
    ElMessage.error('当前用户未加入任何部门')
    }
  }).catch(err => {
    tableLoading.value = false;
  })
};
// æŸ¥çœ‹åˆ¶åº¦ç‰ˆæœ¬åŽ†å²
const viewVersionHistory = (row) => {
  showVersionHistoryDialog.value = true
@@ -582,7 +383,6 @@
}
// æŸ¥çœ‹åˆ¶åº¦è¯¦æƒ…
const viewRegulation = (row) => {
  getList()
  currentRegulationDetail.value = row
  showRegulationDetailDialog.value = true
  getReadingStatusByRuleId(row.id).then(res => {
@@ -659,32 +459,64 @@
  proxy.download('/rulesRegulationsManagement/export', { ...regulationSearchForm }, '规章制度.xlsx')
}
// èŽ·å–å°ç« ç”³è¯·åˆ—è¡¨æ•°æ®
const getSealApplicationList = async () => {
  tableLoading.value = true
  listSealApplication(page,sealSearchForm)
  .then(res => {
    //获取当前登录的部门信息
// èŽ·å–å½“å‰ç™»å½•çš„éƒ¨é—¨ä¿¡æ¯å¹¶è¿‡æ»¤æ•°æ®
    const currentFactoryName = userStore.currentFactoryName
    if (currentFactoryName) {
      // æ ¹æ®currentFactoryName过滤出department相同的数据
      sealApplications.value = res.data.records.filter(item => item.department === currentFactoryName)
      // æ›´æ–°è¿‡æ»¤åŽçš„æ€»æ•°
      page.value.total = sealApplications.value.length
    } else {
      // å¦‚果没有currentFactoryName,则显示所有数据
      sealApplications.value = res.data.records
      page.value.total = res.data.total
    }
    // sealApplications.value = res.data.records
    // page.value.total = res.data.total;
    tableLoading.value = false;
  }).catch(err => {
    tableLoading.value = false;
  })
// é™„件:查询
const fetchRuleFiles = async (rulesRegulationsManagementId) => {
  const params = {
    current: filePage.current,
    size: filePage.size,
    rulesRegulationsManagementId
  }
  const res = await listRuleFiles(params)
  const records = res?.data?.records || []
  filePage.total = res?.data?.total || records.length
  const mapped = records.map(item => ({
    id: item.id,
    name: item.fileName || item.name,
    url: item.fileUrl || item.url,
    raw: item
  }))
  fileListDialogRef.value?.setList(mapped)
}
// æ‰“开附件弹窗
const openFileDialog = async (row) => {
  currentFileRuleId.value = row.id
  fileDialogVisible.value = true
  await fetchRuleFiles(row.id)
}
// åˆ·æ–°é™„件列表
const refreshFileList = async () => {
  if (!currentFileRuleId.value) return
  await fetchRuleFiles(currentFileRuleId.value)
}
// ä¸Šä¼ é™„件(由子组件触发)
const handleAttachmentUpload = async (filePayload) => {
  if (!currentFileRuleId.value) return
  const payload = {
    name: filePayload?.fileName || filePayload?.name,
    url: filePayload?.fileUrl || filePayload?.url,
    rulesRegulationsManagementId: currentFileRuleId.value
  }
  await addRuleFile(payload)
  ElMessage.success('文件上传成功')
  await refreshFileList()
}
// åˆ é™¤é™„ä»¶
const handleAttachmentDelete = async (row) => {
  if (!row?.id) return false
  try {
    await ElMessageBox.confirm('确认删除该附件?', '提示', { type: 'warning' })
  } catch {
    return false
  }
  await delRuleFile([row.id])
  ElMessage.success('删除成功')
  await refreshFileList()
}
// èŽ·å–è§„ç« åˆ¶åº¦åˆ—è¡¨æ•°æ®
const getRegulationList = async () => {
  tableLoading.value = true
@@ -704,7 +536,6 @@
onMounted(() => {
  // åˆå§‹åŒ–
  getSealApplicationList()
  getRegulationList()
})
</script>