Merge remote-tracking branch 'origin/dev_天津军泰伟业' into dev_天津军泰伟业
| | |
| | | <template> |
| | | <div class="qr-code-generator"> |
| | | <!-- 二维码生成表单 --> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="120px" class="qr-form"> |
| | | <el-form :model="form" |
| | | :rules="rules" |
| | | ref="formRef" |
| | | label-width="120px" |
| | | class="qr-form"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="标识类型" prop="type"> |
| | | <el-select v-model="form.type" placeholder="请选择标识类型" style="width: 100%"> |
| | | <el-option label="二维码" value="qrcode"></el-option> |
| | | <el-option label="防伪码" value="security"></el-option> |
| | | <el-form-item label="标识类型" |
| | | prop="type"> |
| | | <el-select v-model="form.type" |
| | | placeholder="请选择标识类型" |
| | | style="width: 100%"> |
| | | <el-option label="二维码" |
| | | value="qrcode"></el-option> |
| | | <el-option label="防伪码" |
| | | value="security"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="内容" prop="content"> |
| | | <el-input |
| | | v-model="form.content" |
| | | placeholder="请输入要编码的内容" |
| | | :type="form.type === 'security' ? 'textarea' : 'text'" |
| | | :rows="form.type === 'security' ? 3 : 1" |
| | | ></el-input> |
| | | <el-form-item label="内容" |
| | | prop="content"> |
| | | <el-input v-model="form.content" |
| | | placeholder="请输入要编码的内容" |
| | | :type="form.type === 'security' ? 'textarea' : 'text'" |
| | | :rows="form.type === 'security' ? 3 : 1"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="尺寸" prop="size"> |
| | | <el-input-number |
| | | v-model="form.size" |
| | | :min="100" |
| | | :max="500" |
| | | :step="50" |
| | | style="width: 100%" |
| | | ></el-input-number> |
| | | <el-form-item label="尺寸" |
| | | prop="size"> |
| | | <el-input-number v-model="form.size" |
| | | :min="100" |
| | | :max="500" |
| | | :step="50" |
| | | style="width: 100%"></el-input-number> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="边距" prop="margin"> |
| | | <el-input-number |
| | | v-model="form.margin" |
| | | :min="0" |
| | | :max="10" |
| | | :step="1" |
| | | style="width: 100%" |
| | | ></el-input-number> |
| | | <el-form-item label="边距" |
| | | prop="margin"> |
| | | <el-input-number v-model="form.margin" |
| | | :min="0" |
| | | :max="10" |
| | | :step="1" |
| | | style="width: 100%"></el-input-number> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="前景色" prop="foregroundColor"> |
| | | <el-color-picker v-model="form.foregroundColor" style="width: 100%"></el-color-picker> |
| | | <el-form-item label="前景色" |
| | | prop="foregroundColor"> |
| | | <el-color-picker v-model="form.foregroundColor" |
| | | style="width: 100%"></el-color-picker> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="背景色" prop="backgroundColor"> |
| | | <el-color-picker v-model="form.backgroundColor" style="width: 100%"></el-color-picker> |
| | | <el-form-item label="背景色" |
| | | prop="backgroundColor"> |
| | | <el-color-picker v-model="form.backgroundColor" |
| | | style="width: 100%"></el-color-picker> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="24"> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="generateCode" :loading="generating"> |
| | | <el-button type="primary" |
| | | @click="generateCode" |
| | | :loading="generating"> |
| | | 生成{{ form.type === 'qrcode' ? '二维码' : '防伪码' }} |
| | | </el-button> |
| | | <el-button @click="resetForm">重置</el-button> |
| | |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | |
| | | <!-- 生成的码显示区域 --> |
| | | <div v-if="generatedCodeUrl" class="code-display"> |
| | | <div v-if="generatedCodeUrl" |
| | | class="code-display"> |
| | | <el-divider content-position="center"> |
| | | {{ form.type === 'qrcode' ? '生成的二维码' : '生成的防伪码' }} |
| | | </el-divider> |
| | | |
| | | <div class="code-container"> |
| | | <div class="code-image"> |
| | | <img :src="generatedCodeUrl" :alt="form.type === 'qrcode' ? '二维码' : '防伪码'" /> |
| | | <img :src="generatedCodeUrl" |
| | | :alt="form.type === 'qrcode' ? '二维码' : '防伪码'" /> |
| | | </div> |
| | | |
| | | <div class="code-info"> |
| | | <p><strong>内容:</strong>{{ form.content }}</p> |
| | | <p><strong>类型:</strong>{{ form.type === 'qrcode' ? '二维码' : '防伪码' }}</p> |
| | |
| | | <p><strong>生成时间:</strong>{{ generateTime }}</p> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="code-actions"> |
| | | <el-button type="success" @click="downloadCode" icon="Download"> |
| | | <el-button type="success" |
| | | @click="downloadCode" |
| | | icon="Download"> |
| | | 下载图片 |
| | | </el-button> |
| | | <el-button type="primary" @click="copyToClipboard" icon="CopyDocument"> |
| | | <el-button type="primary" |
| | | @click="copyToClipboard" |
| | | icon="CopyDocument"> |
| | | 复制内容 |
| | | </el-button> |
| | | <el-button @click="printCode" icon="Printer"> |
| | | <el-button @click="printCode" |
| | | icon="Printer"> |
| | | 打印 |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 批量生成对话框 --> |
| | | <el-dialog v-model="batchDialogVisible" title="批量生成" width="600px"> |
| | | <el-form :model="batchForm" label-width="120px"> |
| | | <el-dialog v-model="batchDialogVisible" |
| | | title="批量生成" |
| | | width="600px"> |
| | | <el-form :model="batchForm" |
| | | label-width="120px"> |
| | | <el-form-item label="生成数量"> |
| | | <el-input-number v-model="batchForm.quantity" :min="1" :max="100" style="width: 100%"></el-input-number> |
| | | <el-input-number v-model="batchForm.quantity" |
| | | :min="1" |
| | | :max="100" |
| | | style="width: 100%"></el-input-number> |
| | | </el-form-item> |
| | | <el-form-item label="前缀"> |
| | | <el-input v-model="batchForm.prefix" placeholder="请输入前缀,如:PROD_"></el-input> |
| | | <el-input v-model="batchForm.prefix" |
| | | placeholder="请输入前缀,如:PROD_"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="起始编号"> |
| | | <el-input-number v-model="batchForm.startNumber" :min="1" style="width: 100%"></el-input-number> |
| | | <el-input-number v-model="batchForm.startNumber" |
| | | :min="1" |
| | | style="width: 100%"></el-input-number> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="generateBatchCodes">开始生成</el-button> |
| | | <el-button @click="batchDialogVisible = false">取消</el-button> |
| | | <el-button type="primary" @click="generateBatchCodes">开始生成</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 批量生成结果 --> |
| | | <div v-if="batchCodes.length > 0" class="batch-results"> |
| | | <div v-if="batchCodes.length > 0" |
| | | class="batch-results"> |
| | | <el-divider content-position="center">批量生成结果</el-divider> |
| | | |
| | | <div class="batch-grid"> |
| | | <div |
| | | v-for="(code, index) in batchCodes" |
| | | :key="index" |
| | | class="batch-item" |
| | | > |
| | | <img :src="code.url" :alt="code.content" /> |
| | | <div v-for="(code, index) in batchCodes" |
| | | :key="index" |
| | | class="batch-item"> |
| | | <img :src="code.url" |
| | | :alt="code.content" /> |
| | | <p class="batch-content">{{ code.content }}</p> |
| | | <el-button size="small" @click="downloadSingleCode(code)">下载</el-button> |
| | | <el-button size="small" |
| | | @click="downloadSingleCode(code)">下载</el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="batch-actions"> |
| | | <el-button type="success" @click="downloadAllCodes">下载全部</el-button> |
| | | <el-button type="success" |
| | | @click="downloadAllCodes">下载全部</el-button> |
| | | <el-button @click="clearBatchCodes">清空结果</el-button> |
| | | </div> |
| | | </div> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed, onMounted } from 'vue' |
| | | import QRCode from 'qrcode' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { Download, CopyDocument, Printer } from '@element-plus/icons-vue' |
| | | import { ref, reactive, computed, onMounted } from "vue"; |
| | | import QRCode from "qrcode"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import { Download, CopyDocument, Printer } from "@element-plus/icons-vue"; |
| | | |
| | | // 定义组件名称 |
| | | defineOptions({ |
| | | name: 'QRCodeGenerator' |
| | | }) |
| | | // 定义组件名称 |
| | | defineOptions({ |
| | | name: "QRCodeGenerator", |
| | | }); |
| | | |
| | | // 表单数据 |
| | | const form = reactive({ |
| | | type: 'qrcode', |
| | | content: '', |
| | | size: 200, |
| | | margin: 2, |
| | | foregroundColor: '#000000', |
| | | backgroundColor: '#FFFFFF' |
| | | }) |
| | | // 表单数据 |
| | | const form = reactive({ |
| | | type: "qrcode", |
| | | content: "", |
| | | size: 200, |
| | | margin: 2, |
| | | foregroundColor: "#000000", |
| | | backgroundColor: "#FFFFFF", |
| | | }); |
| | | |
| | | // 表单验证规则 |
| | | const rules = { |
| | | type: [{ required: true, message: '请选择标识类型', trigger: 'change' }], |
| | | content: [{ required: true, message: '请输入内容', trigger: 'blur' }] |
| | | } |
| | | // 表单验证规则 |
| | | const rules = { |
| | | type: [{ required: true, message: "请选择标识类型", trigger: "change" }], |
| | | content: [{ required: true, message: "请输入内容", trigger: "blur" }], |
| | | }; |
| | | |
| | | // 响应式数据 |
| | | const formRef = ref() |
| | | const generating = ref(false) |
| | | const generatedCodeUrl = ref('') |
| | | const generateTime = ref('') |
| | | const batchDialogVisible = ref(false) |
| | | const batchForm = reactive({ |
| | | quantity: 10, |
| | | prefix: '', |
| | | startNumber: 1 |
| | | }) |
| | | const batchCodes = ref([]) |
| | | // 响应式数据 |
| | | const formRef = ref(); |
| | | const generating = ref(false); |
| | | const generatedCodeUrl = ref(""); |
| | | const generateTime = ref(""); |
| | | const batchDialogVisible = ref(false); |
| | | const batchForm = reactive({ |
| | | quantity: 10, |
| | | prefix: "", |
| | | startNumber: 1, |
| | | }); |
| | | const batchCodes = ref([]); |
| | | |
| | | // 生成二维码或防伪码 |
| | | const generateCode = async () => { |
| | | try { |
| | | await formRef.value.validate() |
| | | |
| | | if (!form.content.trim()) { |
| | | ElMessage.warning('请输入要编码的内容') |
| | | return |
| | | } |
| | | |
| | | generating.value = true |
| | | |
| | | if (form.type === 'qrcode') { |
| | | // 生成二维码 |
| | | generatedCodeUrl.value = await QRCode.toDataURL(form.content, { |
| | | width: form.size, |
| | | margin: form.margin, |
| | | color: { |
| | | dark: form.foregroundColor, |
| | | light: form.backgroundColor |
| | | }, |
| | | errorCorrectionLevel: 'M' |
| | | }) |
| | | } else { |
| | | // 生成防伪码(使用二维码技术,但内容格式不同) |
| | | const securityContent = generateSecurityCode(form.content) |
| | | generatedCodeUrl.value = await QRCode.toDataURL(securityContent, { |
| | | width: form.size, |
| | | margin: form.margin, |
| | | color: { |
| | | dark: form.foregroundColor, |
| | | light: form.backgroundColor |
| | | }, |
| | | errorCorrectionLevel: 'H' // 防伪码使用最高纠错级别 |
| | | }) |
| | | } |
| | | |
| | | generateTime.value = new Date().toLocaleString() |
| | | ElMessage.success('生成成功!') |
| | | |
| | | } catch (error) { |
| | | console.error('生成失败:', error) |
| | | ElMessage.error('生成失败:' + error.message) |
| | | } finally { |
| | | generating.value = false |
| | | } |
| | | } |
| | | // 生成二维码或防伪码 |
| | | const generateCode = async () => { |
| | | try { |
| | | await formRef.value.validate(); |
| | | |
| | | // 生成防伪码内容 |
| | | const generateSecurityCode = (content) => { |
| | | const timestamp = Date.now() |
| | | const random = Math.random().toString(36).substr(2, 8) |
| | | return `SEC_${content}_${timestamp}_${random}` |
| | | } |
| | | if (!form.content.trim()) { |
| | | ElMessage.warning("请输入要编码的内容"); |
| | | return; |
| | | } |
| | | |
| | | // 下载生成的码 |
| | | const downloadCode = () => { |
| | | if (!generatedCodeUrl.value) { |
| | | ElMessage.warning('请先生成码') |
| | | return |
| | | } |
| | | |
| | | const a = document.createElement('a') |
| | | a.href = generatedCodeUrl.value |
| | | a.download = `${form.type === 'qrcode' ? '二维码' : '防伪码'}_${new Date().getTime()}.png` |
| | | document.body.appendChild(a) |
| | | a.click() |
| | | document.body.removeChild(a) |
| | | ElMessage.success('下载成功!') |
| | | } |
| | | generating.value = true; |
| | | |
| | | // 复制内容到剪贴板 |
| | | const copyToClipboard = async () => { |
| | | try { |
| | | await navigator.clipboard.writeText(form.content) |
| | | ElMessage.success('内容已复制到剪贴板') |
| | | } catch (error) { |
| | | // 降级方案 |
| | | const textArea = document.createElement('textarea') |
| | | textArea.value = form.content |
| | | document.body.appendChild(textArea) |
| | | textArea.select() |
| | | document.execCommand('copy') |
| | | document.body.removeChild(textArea) |
| | | ElMessage.success('内容已复制到剪贴板') |
| | | } |
| | | } |
| | | |
| | | // 打印码 |
| | | const printCode = () => { |
| | | if (!generatedCodeUrl.value) { |
| | | ElMessage.warning('请先生成码') |
| | | return |
| | | } |
| | | |
| | | const printWindow = window.open('', '_blank') |
| | | printWindow.document.write(` |
| | | <html> |
| | | <head> |
| | | <title>打印${form.type === 'qrcode' ? '二维码' : '防伪码'}</title> |
| | | <style> |
| | | body { text-align: center; padding: 20px; } |
| | | img { max-width: 100%; height: auto; } |
| | | .info { margin: 20px 0; } |
| | | </style> |
| | | </head> |
| | | <body> |
| | | <h2>${form.type === 'qrcode' ? '二维码' : '防伪码'}</h2> |
| | | <img src="${generatedCodeUrl.value}" alt="${form.type === 'qrcode' ? '二维码' : '防伪码'}" /> |
| | | <div class="info"> |
| | | <p><strong>内容:</strong>${form.content}</p> |
| | | <p><strong>生成时间:</strong>${generateTime.value}</p> |
| | | </div> |
| | | </body> |
| | | </html> |
| | | `) |
| | | printWindow.document.close() |
| | | printWindow.print() |
| | | } |
| | | |
| | | // 重置表单 |
| | | const resetForm = () => { |
| | | formRef.value.resetFields() |
| | | generatedCodeUrl.value = '' |
| | | generateTime.value = '' |
| | | batchCodes.value = [] |
| | | } |
| | | |
| | | // 批量生成 |
| | | const generateBatchCodes = async () => { |
| | | if (!batchForm.prefix.trim()) { |
| | | ElMessage.warning('请输入前缀') |
| | | return |
| | | } |
| | | |
| | | batchCodes.value = [] |
| | | generating.value = true |
| | | |
| | | try { |
| | | for (let i = 0; i < batchForm.quantity; i++) { |
| | | const number = batchForm.startNumber + i |
| | | const content = `${batchForm.prefix}${number.toString().padStart(6, '0')}` |
| | | |
| | | let codeUrl |
| | | if (form.type === 'qrcode') { |
| | | codeUrl = await QRCode.toDataURL(content, { |
| | | if (form.type === "qrcode") { |
| | | // 生成二维码 |
| | | generatedCodeUrl.value = await QRCode.toDataURL(form.content, { |
| | | width: form.size, |
| | | margin: form.margin, |
| | | color: { |
| | | dark: form.foregroundColor, |
| | | light: form.backgroundColor |
| | | } |
| | | }) |
| | | light: form.backgroundColor, |
| | | }, |
| | | errorCorrectionLevel: "M", |
| | | }); |
| | | } else { |
| | | const securityContent = generateSecurityCode(content) |
| | | codeUrl = await QRCode.toDataURL(securityContent, { |
| | | // 生成防伪码(使用二维码技术,但内容格式不同) |
| | | const securityContent = generateSecurityCode(form.content); |
| | | generatedCodeUrl.value = await QRCode.toDataURL(securityContent, { |
| | | width: form.size, |
| | | margin: form.margin, |
| | | color: { |
| | | dark: form.foregroundColor, |
| | | light: form.backgroundColor |
| | | } |
| | | }) |
| | | light: form.backgroundColor, |
| | | }, |
| | | errorCorrectionLevel: "H", // 防伪码使用最高纠错级别 |
| | | }); |
| | | } |
| | | |
| | | batchCodes.value.push({ |
| | | content, |
| | | url: codeUrl |
| | | }) |
| | | |
| | | generateTime.value = new Date().toLocaleString(); |
| | | ElMessage.success("生成成功!"); |
| | | } catch (error) { |
| | | console.error("生成失败:", error); |
| | | ElMessage.error("生成失败:" + error.message); |
| | | } finally { |
| | | generating.value = false; |
| | | } |
| | | |
| | | ElMessage.success(`批量生成完成,共生成 ${batchForm.quantity} 个码`) |
| | | batchDialogVisible.value = false |
| | | |
| | | } catch (error) { |
| | | console.error('批量生成失败:', error) |
| | | ElMessage.error('批量生成失败:' + error.message) |
| | | } finally { |
| | | generating.value = false |
| | | } |
| | | } |
| | | }; |
| | | |
| | | // 下载单个批量生成的码 |
| | | const downloadSingleCode = (code) => { |
| | | const a = document.createElement('a') |
| | | a.href = code.url |
| | | a.download = `${code.content}.png` |
| | | document.body.appendChild(a) |
| | | a.click() |
| | | document.body.removeChild(a) |
| | | } |
| | | // 生成防伪码内容 |
| | | const generateSecurityCode = content => { |
| | | const timestamp = Date.now(); |
| | | const random = Math.random().toString(36).substr(2, 8); |
| | | return `SEC_${content}_${timestamp}_${random}`; |
| | | }; |
| | | |
| | | // 下载所有批量生成的码 |
| | | const downloadAllCodes = async () => { |
| | | if (batchCodes.value.length === 0) { |
| | | ElMessage.warning('没有可下载的码') |
| | | return |
| | | } |
| | | |
| | | try { |
| | | // 使用JSZip打包下载 |
| | | const JSZip = await import('jszip') |
| | | const zip = new JSZip.default() |
| | | |
| | | batchCodes.value.forEach((code, index) => { |
| | | // 将base64转换为blob |
| | | const base64Data = code.url.split(',')[1] |
| | | const byteCharacters = atob(base64Data) |
| | | const byteNumbers = new Array(byteCharacters.length) |
| | | for (let i = 0; i < byteCharacters.length; i++) { |
| | | byteNumbers[i] = byteCharacters.charCodeAt(i) |
| | | // 下载生成的码 |
| | | const downloadCode = () => { |
| | | if (!generatedCodeUrl.value) { |
| | | ElMessage.warning("请先生成码"); |
| | | return; |
| | | } |
| | | |
| | | const a = document.createElement("a"); |
| | | a.href = generatedCodeUrl.value; |
| | | a.download = `${ |
| | | form.type === "qrcode" ? "二维码" : "防伪码" |
| | | }_${new Date().getTime()}.png`; |
| | | document.body.appendChild(a); |
| | | a.click(); |
| | | document.body.removeChild(a); |
| | | ElMessage.success("下载成功!"); |
| | | }; |
| | | |
| | | // 复制内容到剪贴板 |
| | | const copyToClipboard = async () => { |
| | | try { |
| | | await navigator.clipboard.writeText(form.content); |
| | | ElMessage.success("内容已复制到剪贴板"); |
| | | } catch (error) { |
| | | // 降级方案 |
| | | const textArea = document.createElement("textarea"); |
| | | textArea.value = form.content; |
| | | document.body.appendChild(textArea); |
| | | textArea.select(); |
| | | document.execCommand("copy"); |
| | | document.body.removeChild(textArea); |
| | | ElMessage.success("内容已复制到剪贴板"); |
| | | } |
| | | }; |
| | | |
| | | // 打印码 |
| | | const printCode = () => { |
| | | if (!generatedCodeUrl.value) { |
| | | ElMessage.warning("请先生成码"); |
| | | return; |
| | | } |
| | | |
| | | const printWindow = window.open("", "_blank"); |
| | | printWindow.document.write(` |
| | | <html> |
| | | <head> |
| | | <title>打印${form.type === "qrcode" ? "二维码" : "防伪码"}</title> |
| | | <style> |
| | | body { text-align: center; padding: 20px; } |
| | | img { max-width: 100%; height: auto; } |
| | | .info { margin: 20px 0; } |
| | | </style> |
| | | </head> |
| | | <body> |
| | | <h2>${form.type === "qrcode" ? "二维码" : "防伪码"}</h2> |
| | | <img src="${generatedCodeUrl.value}" alt="${ |
| | | form.type === "qrcode" ? "二维码" : "防伪码" |
| | | }" /> |
| | | <div class="info"> |
| | | <p><strong>内容:</strong>${form.content}</p> |
| | | <p><strong>生成时间:</strong>${generateTime.value}</p> |
| | | </div> |
| | | </body> |
| | | </html> |
| | | `); |
| | | printWindow.document.close(); |
| | | printWindow.print(); |
| | | }; |
| | | |
| | | // 重置表单 |
| | | const resetForm = () => { |
| | | formRef.value.resetFields(); |
| | | generatedCodeUrl.value = ""; |
| | | generateTime.value = ""; |
| | | batchCodes.value = []; |
| | | }; |
| | | |
| | | // 批量生成 |
| | | const generateBatchCodes = async () => { |
| | | if (!batchForm.prefix.trim()) { |
| | | ElMessage.warning("请输入前缀"); |
| | | return; |
| | | } |
| | | |
| | | batchCodes.value = []; |
| | | generating.value = true; |
| | | |
| | | try { |
| | | for (let i = 0; i < batchForm.quantity; i++) { |
| | | const number = batchForm.startNumber + i; |
| | | const content = `${batchForm.prefix}${number |
| | | .toString() |
| | | .padStart(6, "0")}`; |
| | | |
| | | let codeUrl; |
| | | if (form.type === "qrcode") { |
| | | codeUrl = await QRCode.toDataURL(content, { |
| | | width: form.size, |
| | | margin: form.margin, |
| | | color: { |
| | | dark: form.foregroundColor, |
| | | light: form.backgroundColor, |
| | | }, |
| | | }); |
| | | } else { |
| | | const securityContent = generateSecurityCode(content); |
| | | codeUrl = await QRCode.toDataURL(securityContent, { |
| | | width: form.size, |
| | | margin: form.margin, |
| | | color: { |
| | | dark: form.foregroundColor, |
| | | light: form.backgroundColor, |
| | | }, |
| | | }); |
| | | } |
| | | |
| | | batchCodes.value.push({ |
| | | content, |
| | | url: codeUrl, |
| | | }); |
| | | } |
| | | const byteArray = new Uint8Array(byteNumbers) |
| | | |
| | | zip.file(`${code.content}.png`, byteArray) |
| | | }) |
| | | |
| | | const content = await zip.generateAsync({ type: 'blob' }) |
| | | const a = document.createElement('a') |
| | | a.href = URL.createObjectURL(content) |
| | | a.download = `批量${form.type === 'qrcode' ? '二维码' : '防伪码'}_${new Date().getTime()}.zip` |
| | | document.body.appendChild(a) |
| | | a.click() |
| | | document.body.removeChild(a) |
| | | URL.revokeObjectURL(a.href) |
| | | |
| | | ElMessage.success('批量下载完成!') |
| | | } catch (error) { |
| | | console.error('批量下载失败:', error) |
| | | ElMessage.error('批量下载失败,请逐个下载') |
| | | } |
| | | } |
| | | |
| | | // 清空批量生成结果 |
| | | const clearBatchCodes = () => { |
| | | batchCodes.value = [] |
| | | } |
| | | ElMessage.success(`批量生成完成,共生成 ${batchForm.quantity} 个码`); |
| | | batchDialogVisible.value = false; |
| | | } catch (error) { |
| | | console.error("批量生成失败:", error); |
| | | ElMessage.error("批量生成失败:" + error.message); |
| | | } finally { |
| | | generating.value = false; |
| | | } |
| | | }; |
| | | |
| | | // 暴露方法给父组件 |
| | | defineExpose({ |
| | | generateCode, |
| | | downloadCode, |
| | | resetForm, |
| | | form |
| | | }) |
| | | // 下载单个批量生成的码 |
| | | const downloadSingleCode = code => { |
| | | const a = document.createElement("a"); |
| | | a.href = code.url; |
| | | a.download = `${code.content}.png`; |
| | | document.body.appendChild(a); |
| | | a.click(); |
| | | document.body.removeChild(a); |
| | | }; |
| | | |
| | | // 下载所有批量生成的码 |
| | | const downloadAllCodes = async () => { |
| | | if (batchCodes.value.length === 0) { |
| | | ElMessage.warning("没有可下载的码"); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | // 使用JSZip打包下载 |
| | | const JSZip = await import("jszip"); |
| | | const zip = new JSZip.default(); |
| | | |
| | | batchCodes.value.forEach((code, index) => { |
| | | // 将base64转换为blob |
| | | const base64Data = code.url.split(",")[1]; |
| | | const byteCharacters = atob(base64Data); |
| | | const byteNumbers = new Array(byteCharacters.length); |
| | | for (let i = 0; i < byteCharacters.length; i++) { |
| | | byteNumbers[i] = byteCharacters.charCodeAt(i); |
| | | } |
| | | const byteArray = new Uint8Array(byteNumbers); |
| | | |
| | | zip.file(`${code.content}.png`, byteArray); |
| | | }); |
| | | |
| | | const content = await zip.generateAsync({ type: "blob" }); |
| | | const a = document.createElement("a"); |
| | | a.href = URL.createObjectURL(content); |
| | | a.download = `批量${ |
| | | form.type === "qrcode" ? "二维码" : "防伪码" |
| | | }_${new Date().getTime()}.zip`; |
| | | document.body.appendChild(a); |
| | | a.click(); |
| | | document.body.removeChild(a); |
| | | URL.revokeObjectURL(a.href); |
| | | |
| | | ElMessage.success("批量下载完成!"); |
| | | } catch (error) { |
| | | console.error("批量下载失败:", error); |
| | | ElMessage.error("批量下载失败,请逐个下载"); |
| | | } |
| | | }; |
| | | |
| | | // 清空批量生成结果 |
| | | const clearBatchCodes = () => { |
| | | batchCodes.value = []; |
| | | }; |
| | | |
| | | // 暴露方法给父组件 |
| | | defineExpose({ |
| | | generateCode, |
| | | downloadCode, |
| | | resetForm, |
| | | form, |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .qr-code-generator { |
| | | padding: 20px; |
| | | } |
| | | .qr-code-generator { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .qr-form { |
| | | background: #f8f9fa; |
| | | padding: 20px; |
| | | border-radius: 8px; |
| | | margin-bottom: 20px; |
| | | } |
| | | .qr-form { |
| | | background: #f8f9fa; |
| | | padding: 20px; |
| | | border-radius: 8px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .code-display { |
| | | margin-top: 30px; |
| | | } |
| | | .code-display { |
| | | margin-top: 30px; |
| | | } |
| | | |
| | | .code-container { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: flex-start; |
| | | gap: 40px; |
| | | margin: 20px 0; |
| | | } |
| | | |
| | | .code-image img { |
| | | border: 2px solid #e0e0e0; |
| | | border-radius: 8px; |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .code-info { |
| | | text-align: left; |
| | | min-width: 200px; |
| | | } |
| | | |
| | | .code-info p { |
| | | margin: 8px 0; |
| | | color: #666; |
| | | } |
| | | |
| | | .code-actions { |
| | | text-align: center; |
| | | margin: 20px 0; |
| | | } |
| | | |
| | | .code-actions .el-button { |
| | | margin: 0 10px; |
| | | } |
| | | |
| | | .batch-results { |
| | | margin-top: 30px; |
| | | } |
| | | |
| | | .batch-grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); |
| | | gap: 20px; |
| | | margin: 20px 0; |
| | | } |
| | | |
| | | .batch-item { |
| | | text-align: center; |
| | | padding: 15px; |
| | | border: 1px solid #e0e0e0; |
| | | border-radius: 8px; |
| | | background: #fff; |
| | | } |
| | | |
| | | .batch-item img { |
| | | width: 100px; |
| | | height: 100px; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .batch-content { |
| | | font-size: 12px; |
| | | color: #666; |
| | | margin: 10px 0; |
| | | word-break: break-all; |
| | | } |
| | | |
| | | .batch-actions { |
| | | text-align: center; |
| | | margin: 20px 0; |
| | | } |
| | | |
| | | .batch-actions .el-button { |
| | | margin: 0 10px; |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | .code-container { |
| | | flex-direction: column; |
| | | align-items: center; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: flex-start; |
| | | gap: 40px; |
| | | margin: 20px 0; |
| | | } |
| | | |
| | | |
| | | .code-image img { |
| | | border: 2px solid #e0e0e0; |
| | | border-radius: 8px; |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .code-info { |
| | | text-align: left; |
| | | min-width: 200px; |
| | | } |
| | | |
| | | .code-info p { |
| | | margin: 8px 0; |
| | | color: #666; |
| | | } |
| | | |
| | | .code-actions { |
| | | text-align: center; |
| | | margin: 20px 0; |
| | | } |
| | | |
| | | .code-actions .el-button { |
| | | margin: 0 10px; |
| | | } |
| | | |
| | | .batch-results { |
| | | margin-top: 30px; |
| | | } |
| | | |
| | | .batch-grid { |
| | | grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); |
| | | gap: 20px; |
| | | margin: 20px 0; |
| | | } |
| | | } |
| | | |
| | | .batch-item { |
| | | text-align: center; |
| | | padding: 15px; |
| | | border: 1px solid #e0e0e0; |
| | | border-radius: 8px; |
| | | background: #fff; |
| | | } |
| | | |
| | | .batch-item img { |
| | | width: 100px; |
| | | height: 100px; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .batch-content { |
| | | font-size: 12px; |
| | | color: #666; |
| | | margin: 10px 0; |
| | | word-break: break-all; |
| | | } |
| | | |
| | | .batch-actions { |
| | | text-align: center; |
| | | margin: 20px 0; |
| | | } |
| | | |
| | | .batch-actions .el-button { |
| | | margin: 0 10px; |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | .code-container { |
| | | flex-direction: column; |
| | | align-items: center; |
| | | } |
| | | |
| | | .batch-grid { |
| | | grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); |
| | | } |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-tabs v-model="activeTab" type="border-card"> |
| | | <el-tabs v-model="activeTab" |
| | | type="border-card"> |
| | | <!-- 假期设置 --> |
| | | <el-tab-pane label="假期设置" name="holiday"> |
| | | <el-tab-pane label="假期设置" |
| | | name="holiday"> |
| | | <div class="tab-content"> |
| | | <el-button type="primary" @click="openDialog('holiday', 'add')">新增假期</el-button> |
| | | |
| | | <el-table :data="holidayData" border style="width: 100%; margin-top: 20px;"> |
| | | <el-table-column prop="name" label="假期名称" /> |
| | | <el-table-column prop="type" label="假期类型"> |
| | | <el-button type="primary" |
| | | @click="openDialog('holiday', 'add')">新增假期</el-button> |
| | | <el-table :data="holidayData" |
| | | border |
| | | style="width: 100%; margin-top: 20px;"> |
| | | <el-table-column prop="name" |
| | | label="假期名称" /> |
| | | <el-table-column prop="type" |
| | | label="假期类型"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getTagType(scope.row.type)">{{ getTypeLabel(scope.row.type) }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="startDate" label="开始日期" /> |
| | | <el-table-column prop="endDate" label="结束日期" /> |
| | | <el-table-column prop="days" label="天数" align="center" /> |
| | | <el-table-column prop="status" label="状态" > |
| | | <el-table-column prop="startDate" |
| | | label="开始日期" /> |
| | | <el-table-column prop="endDate" |
| | | label="结束日期" /> |
| | | <el-table-column prop="days" |
| | | label="天数" |
| | | align="center" /> |
| | | <el-table-column prop="status" |
| | | label="状态"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'"> |
| | | {{ scope.row.status === 'active' ? '启用' : '停用' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" fixed="right"> |
| | | <el-table-column label="操作" |
| | | fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button type="primary" size="small" @click="openDialog('holiday', 'edit', scope.row)">编辑</el-button> |
| | | <el-button type="danger" size="small" @click="deleteItem('holiday', scope.row)">删除</el-button> |
| | | <el-button type="primary" |
| | | size="small" |
| | | @click="openDialog('holiday', 'edit', scope.row)">编辑</el-button> |
| | | <el-button type="danger" |
| | | size="small" |
| | | @click="deleteItem('holiday', scope.row)">删除</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </el-tab-pane> |
| | | |
| | | <!-- 年假设置 --> |
| | | <el-tab-pane label="年假设置" name="annual"> |
| | | <el-tab-pane label="年假设置" |
| | | name="annual"> |
| | | <div class="tab-content"> |
| | | <el-button type="primary" @click="openDialog('annual', 'add')">新增年假规则</el-button> |
| | | |
| | | <el-table :data="annualData" border style="width: 100%; margin-top: 20px;"> |
| | | <el-table-column prop="employeeType" label="员工类型"> |
| | | <el-button type="primary" |
| | | @click="openDialog('annual', 'add')">新增年假规则</el-button> |
| | | <el-table :data="annualData" |
| | | border |
| | | style="width: 100%; margin-top: 20px;"> |
| | | <el-table-column prop="employeeType" |
| | | label="员工类型"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getTagType(scope.row.employeeType)">{{ getTypeLabel(scope.row.employeeType) }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="workYears" label="工作年限" /> |
| | | <el-table-column prop="annualDays" label="年假天数" align="center" /> |
| | | <el-table-column prop="maxCarryOver" label="最大结转天数" align="center" /> |
| | | <el-table-column prop="status" label="状态"> |
| | | <el-table-column prop="workYears" |
| | | label="工作年限" /> |
| | | <el-table-column prop="annualDays" |
| | | label="年假天数" |
| | | align="center" /> |
| | | <el-table-column prop="maxCarryOver" |
| | | label="最大结转天数" |
| | | align="center" /> |
| | | <el-table-column prop="status" |
| | | label="状态"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'"> |
| | | {{ scope.row.status === 'active' ? '启用' : '停用' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" fixed="right"> |
| | | <el-table-column label="操作" |
| | | fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button type="primary" size="small" @click="openDialog('annual', 'edit', scope.row)">编辑</el-button> |
| | | <el-button type="danger" size="small" @click="deleteItem('annual', scope.row)">删除</el-button> |
| | | <el-button type="primary" |
| | | size="small" |
| | | @click="openDialog('annual', 'edit', scope.row)">编辑</el-button> |
| | | <el-button type="danger" |
| | | size="small" |
| | | @click="deleteItem('annual', scope.row)">删除</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </el-tab-pane> |
| | | |
| | | <!-- 加班设置 --> |
| | | <el-tab-pane label="加班设置" name="overtime"> |
| | | <el-tab-pane label="加班设置" |
| | | name="overtime"> |
| | | <div class="tab-content"> |
| | | <el-button type="primary" @click="openDialog('overtime', 'add')">新增加班规则</el-button> |
| | | |
| | | <el-table :data="overtimeData" border style="width: 100%; margin-top: 20px;"> |
| | | <el-table-column prop="name" label="规则名称" /> |
| | | <el-table-column prop="type" label="加班类型" > |
| | | <el-button type="primary" |
| | | @click="openDialog('overtime', 'add')">新增加班规则</el-button> |
| | | <el-table :data="overtimeData" |
| | | border |
| | | style="width: 100%; margin-top: 20px;"> |
| | | <el-table-column prop="name" |
| | | label="规则名称" /> |
| | | <el-table-column prop="type" |
| | | label="加班类型"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getTagType(scope.row.type)">{{ getTypeLabel(scope.row.type) }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="startTime" label="开始时间" /> |
| | | <el-table-column prop="endTime" label="结束时间" /> |
| | | <el-table-column prop="rate" label="倍率" align="center" /> |
| | | <el-table-column prop="status" label="状态" > |
| | | <el-table-column prop="startTime" |
| | | label="开始时间" /> |
| | | <el-table-column prop="endTime" |
| | | label="结束时间" /> |
| | | <el-table-column prop="rate" |
| | | label="倍率" |
| | | align="center" /> |
| | | <el-table-column prop="status" |
| | | label="状态"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'"> |
| | | {{ scope.row.status === 'active' ? '启用' : '停用' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" fixed="right"> |
| | | <el-table-column label="操作" |
| | | fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button type="primary" size="small" @click="openDialog('overtime', 'edit', scope.row)">编辑</el-button> |
| | | <el-button type="danger" size="small" @click="deleteItem('overtime', scope.row)">删除</el-button> |
| | | <el-button type="primary" |
| | | size="small" |
| | | @click="openDialog('overtime', 'edit', scope.row)">编辑</el-button> |
| | | <el-button type="danger" |
| | | size="small" |
| | | @click="deleteItem('overtime', scope.row)">删除</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </el-tab-pane> |
| | | |
| | | <!-- 上班时间设置 --> |
| | | <el-tab-pane label="上班时间设置" name="worktime"> |
| | | <el-tab-pane label="上班时间设置" |
| | | name="worktime"> |
| | | <div class="tab-content"> |
| | | <el-button type="primary" @click="openDialog('worktime', 'add')">新增时间段</el-button> |
| | | |
| | | <el-table :data="worktimeData" border style="width: 100%; margin-top: 20px;"> |
| | | <el-table-column prop="name" label="时间段名称" /> |
| | | <el-table-column prop="startTime" label="上班时间"/> |
| | | <el-table-column prop="endTime" label="下班时间" /> |
| | | <el-table-column prop="flexibleStart" label="弹性上班"> |
| | | <el-button type="primary" |
| | | @click="openDialog('worktime', 'add')">新增时间段</el-button> |
| | | <el-table :data="worktimeData" |
| | | border |
| | | style="width: 100%; margin-top: 20px;"> |
| | | <el-table-column prop="name" |
| | | label="时间段名称" /> |
| | | <el-table-column prop="startTime" |
| | | label="上班时间" /> |
| | | <el-table-column prop="endTime" |
| | | label="下班时间" /> |
| | | <el-table-column prop="flexibleStart" |
| | | label="弹性上班"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.flexibleStart === 'true' ? 'success' : 'info'"> |
| | | {{ scope.row.flexibleStart === 'true' ? '是' : '否' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="flexibleMinutes" label="弹性时间(分钟)" width="120" align="center" /> |
| | | <el-table-column prop="status" label="状态" > |
| | | <el-table-column prop="flexibleMinutes" |
| | | label="弹性时间(分钟)" |
| | | width="120" |
| | | align="center" /> |
| | | <el-table-column prop="status" |
| | | label="状态"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'"> |
| | | {{ scope.row.status === 'active' ? '启用' : '停用' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" fixed="right"> |
| | | <el-table-column label="操作" |
| | | fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button type="primary" size="small" @click="openDialog('worktime', 'edit', scope.row)">编辑</el-button> |
| | | <el-button type="danger" size="small" @click="deleteItem('worktime', scope.row)">删除</el-button> |
| | | <el-button type="primary" |
| | | size="small" |
| | | @click="openDialog('worktime', 'edit', scope.row)">编辑</el-button> |
| | | <el-button type="danger" |
| | | size="small" |
| | | @click="deleteItem('worktime', scope.row)">删除</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </el-tab-pane> |
| | | |
| | | <!-- 打卡记录 --> |
| | | <el-tab-pane label="打卡记录" name="attendance"> |
| | | <el-tab-pane label="打卡记录" |
| | | name="attendance"> |
| | | <div class="tab-content"> |
| | | <div style="margin-bottom: 20px;"> |
| | | <el-date-picker |
| | | v-model="attendanceDate" |
| | | type="date" |
| | | placeholder="选择日期" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | style="margin-right: 10px;" |
| | | @change="filterAttendanceData" |
| | | /> |
| | | <el-select |
| | | v-model="attendanceStatus" |
| | | placeholder="选择状态" |
| | | style="width: 120px; margin-right: 10px;" |
| | | @change="filterAttendanceData" |
| | | > |
| | | <el-option label="全部" value="" /> |
| | | <el-option label="正常" value="normal" /> |
| | | <el-option label="迟到" value="late" /> |
| | | <el-option label="早退" value="early" /> |
| | | <el-option label="缺勤" value="absent" /> |
| | | <el-date-picker v-model="attendanceDate" |
| | | type="date" |
| | | placeholder="选择日期" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | style="margin-right: 10px;" |
| | | @change="filterAttendanceData" /> |
| | | <el-select v-model="attendanceStatus" |
| | | placeholder="选择状态" |
| | | style="width: 120px; margin-right: 10px;" |
| | | @change="filterAttendanceData"> |
| | | <el-option label="全部" |
| | | value="" /> |
| | | <el-option label="正常" |
| | | value="normal" /> |
| | | <el-option label="迟到" |
| | | value="late" /> |
| | | <el-option label="早退" |
| | | value="early" /> |
| | | <el-option label="缺勤" |
| | | value="absent" /> |
| | | </el-select> |
| | | <el-button type="primary" @click="exportAttendance">导出记录</el-button> |
| | | <el-button type="primary" |
| | | @click="exportAttendance">导出记录</el-button> |
| | | </div> |
| | | |
| | | <el-table :data="filteredAttendanceData" border style="width: 100%;"> |
| | | <el-table-column prop="employeeName" label="员工姓名" width="120" /> |
| | | <el-table-column prop="department" label="部门" width="120" /> |
| | | <el-table-column prop="date" label="日期" width="120" /> |
| | | <el-table-column prop="clockInTime" label="上班打卡" width="120" /> |
| | | <el-table-column prop="clockOutTime" label="下班打卡" width="120" /> |
| | | <el-table-column prop="workHours" label="工作时长" width="100" align="center" /> |
| | | <el-table-column prop="status" label="状态" width="100" align="center"> |
| | | <el-table :data="filteredAttendanceData" |
| | | border |
| | | style="width: 100%;"> |
| | | <el-table-column prop="employeeName" |
| | | label="员工姓名" |
| | | width="120" /> |
| | | <el-table-column prop="department" |
| | | label="部门" |
| | | width="120" /> |
| | | <el-table-column prop="date" |
| | | label="日期" |
| | | width="120" /> |
| | | <el-table-column prop="clockInTime" |
| | | label="上班打卡" |
| | | width="120" /> |
| | | <el-table-column prop="clockOutTime" |
| | | label="下班打卡" |
| | | width="120" /> |
| | | <el-table-column prop="workHours" |
| | | label="工作时长" |
| | | width="100" |
| | | align="center" /> |
| | | <el-table-column prop="status" |
| | | label="状态" |
| | | width="100" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getAttendanceTagType(scope.row.status)">{{ getAttendanceStatusLabel(scope.row.status) }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="location" label="打卡地点" width="150" /> |
| | | <el-table-column prop="remark" label="备注" min-width="150" /> |
| | | <el-table-column prop="location" |
| | | label="打卡地点" |
| | | width="150" /> |
| | | <el-table-column prop="remark" |
| | | label="备注" |
| | | min-width="150" /> |
| | | </el-table> |
| | | </div> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | |
| | | <!-- 通用弹窗 --> |
| | | <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px"> |
| | | <el-form ref="formRef" :model="form" :rules="rules" label-width="100px"> |
| | | <el-form-item label="名称" prop="name" v-if="currentType !== 'annual'"> |
| | | <el-input v-model="form.name" placeholder="请输入名称" /> |
| | | <el-dialog v-model="dialogVisible" |
| | | :title="dialogTitle" |
| | | width="600px"> |
| | | <el-form ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="100px"> |
| | | <el-form-item label="名称" |
| | | prop="name" |
| | | v-if="currentType !== 'annual'"> |
| | | <el-input v-model="form.name" |
| | | placeholder="请输入名称" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="类型" prop="type" v-if="currentType === 'holiday' || currentType === 'overtime'"> |
| | | <el-select v-model="form.type" placeholder="请选择类型" style="width: 100%"> |
| | | <el-option |
| | | v-for="option in getTypeOptions()" |
| | | :key="option.value" |
| | | :label="option.label" |
| | | :value="option.value" |
| | | /> |
| | | <el-form-item label="类型" |
| | | prop="type" |
| | | v-if="currentType === 'holiday' || currentType === 'overtime'"> |
| | | <el-select v-model="form.type" |
| | | placeholder="请选择类型" |
| | | style="width: 100%"> |
| | | <el-option v-for="option in getTypeOptions()" |
| | | :key="option.value" |
| | | :label="option.label" |
| | | :value="option.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="员工类型" prop="employeeType" v-if="currentType === 'annual'"> |
| | | <el-select v-model="form.employeeType" placeholder="请选择员工类型" style="width: 100%"> |
| | | <el-form-item label="员工类型" |
| | | prop="employeeType" |
| | | v-if="currentType === 'annual'"> |
| | | <el-select v-model="form.employeeType" |
| | | placeholder="请选择员工类型" |
| | | style="width: 100%"> |
| | | <!-- <el-option label="正式员工" value="regular" /> |
| | | <el-option label="试用期员工" value="probation" /> |
| | | <el-option label="实习生" value="intern" /> --> |
| | | <el-option |
| | | v-for="option in getTypeOptions()" |
| | | :key="option.value" |
| | | :label="option.label" |
| | | :value="option.value" |
| | | /> |
| | | <el-option v-for="option in getTypeOptions()" |
| | | :key="option.value" |
| | | :label="option.label" |
| | | :value="option.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="工作年限" prop="workYears" v-if="currentType === 'annual'"> |
| | | <el-input v-model="form.workYears" placeholder="如:1-3年、3-5年等" /> |
| | | <el-form-item label="工作年限" |
| | | prop="workYears" |
| | | v-if="currentType === 'annual'"> |
| | | <el-input v-model="form.workYears" |
| | | placeholder="如:1-3年、3-5年等" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="年假天数" prop="annualDays" v-if="currentType === 'annual'"> |
| | | <el-input-number v-model="form.annualDays" :min="0" :max="365" style="width: 100%" /> |
| | | <el-form-item label="年假天数" |
| | | prop="annualDays" |
| | | v-if="currentType === 'annual'"> |
| | | <el-input-number v-model="form.annualDays" |
| | | :min="0" |
| | | :max="365" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="最大结转天数" prop="maxCarryOver" v-if="currentType === 'annual'"> |
| | | <el-input-number v-model="form.maxCarryOver" :min="0" :max="30" style="width: 100%" /> |
| | | <el-form-item label="最大结转天数" |
| | | prop="maxCarryOver" |
| | | v-if="currentType === 'annual'"> |
| | | <el-input-number v-model="form.maxCarryOver" |
| | | :min="0" |
| | | :max="30" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="日期范围" prop="dateRange" v-if="currentType === 'holiday'"> |
| | | <el-date-picker |
| | | v-model="form.dateRange" |
| | | type="daterange" |
| | | range-separator="至" |
| | | start-placeholder="开始日期" |
| | | end-placeholder="结束日期" |
| | | style="width: 100%" |
| | | @change="calculateDays" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="天数" prop="days" v-if="currentType === 'holiday'"> |
| | | <el-input-number v-model="form.days" :min="0" style="width: 100%" /> |
| | | <el-form-item label="日期范围" |
| | | prop="dateRange" |
| | | v-if="currentType === 'holiday'"> |
| | | <el-date-picker v-model="form.dateRange" |
| | | type="daterange" |
| | | range-separator="至" |
| | | start-placeholder="开始日期" |
| | | end-placeholder="结束日期" |
| | | style="width: 100%" |
| | | @change="calculateDays" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="开始时间" prop="startTime" v-if="currentType === 'overtime'"> |
| | | <el-time-picker |
| | | v-model="form.startTime" |
| | | placeholder="开始时间" |
| | | format="HH:mm" |
| | | value-format="HH:mm" |
| | | style="width: 100%" |
| | | @change="validateTimeField('startTime')" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="结束时间" prop="endTime" v-if="currentType === 'overtime'"> |
| | | <el-time-picker |
| | | v-model="form.endTime" |
| | | placeholder="结束时间" |
| | | format="HH:mm" |
| | | value-format="HH:mm" |
| | | style="width: 100%" |
| | | @change="validateTimeField('endTime')" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="倍率" prop="rate" v-if="currentType === 'overtime'"> |
| | | <el-input-number v-model="form.rate" :min="1" :max="3" :step="0.5" style="width: 100%" /> |
| | | <el-form-item label="天数" |
| | | prop="days" |
| | | v-if="currentType === 'holiday'"> |
| | | <el-input-number v-model="form.days" |
| | | :min="0" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="上班时间" prop="workStartTime" v-if="currentType === 'worktime'"> |
| | | <el-time-picker |
| | | v-model="form.workStartTime" |
| | | placeholder="上班时间" |
| | | format="HH:mm" |
| | | value-format="HH:mm" |
| | | style="width: 100%" |
| | | @change="validateTimeField('workStartTime')" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="下班时间" prop="workEndTime" v-if="currentType === 'worktime'"> |
| | | <el-time-picker |
| | | v-model="form.workEndTime" |
| | | placeholder="下班时间" |
| | | format="HH:mm" |
| | | value-format="HH:mm" |
| | | style="width: 100%" |
| | | @change="validateTimeField('workEndTime')" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="弹性上班" prop="flexibleStart" v-if="currentType === 'worktime'"> |
| | | <el-form-item label="开始时间" |
| | | prop="startTime" |
| | | v-if="currentType === 'overtime'"> |
| | | <el-time-picker v-model="form.startTime" |
| | | placeholder="开始时间" |
| | | format="HH:mm" |
| | | value-format="HH:mm" |
| | | style="width: 100%" |
| | | @change="validateTimeField('startTime')" /> |
| | | </el-form-item> |
| | | <el-form-item label="结束时间" |
| | | prop="endTime" |
| | | v-if="currentType === 'overtime'"> |
| | | <el-time-picker v-model="form.endTime" |
| | | placeholder="结束时间" |
| | | format="HH:mm" |
| | | value-format="HH:mm" |
| | | style="width: 100%" |
| | | @change="validateTimeField('endTime')" /> |
| | | </el-form-item> |
| | | <el-form-item label="倍率" |
| | | prop="rate" |
| | | v-if="currentType === 'overtime'"> |
| | | <el-input-number v-model="form.rate" |
| | | :min="1" |
| | | :max="3" |
| | | :step="0.5" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="上班时间" |
| | | prop="workStartTime" |
| | | v-if="currentType === 'worktime'"> |
| | | <el-time-picker v-model="form.workStartTime" |
| | | placeholder="上班时间" |
| | | format="HH:mm" |
| | | value-format="HH:mm" |
| | | style="width: 100%" |
| | | @change="validateTimeField('workStartTime')" /> |
| | | </el-form-item> |
| | | <el-form-item label="下班时间" |
| | | prop="workEndTime" |
| | | v-if="currentType === 'worktime'"> |
| | | <el-time-picker v-model="form.workEndTime" |
| | | placeholder="下班时间" |
| | | format="HH:mm" |
| | | value-format="HH:mm" |
| | | style="width: 100%" |
| | | @change="validateTimeField('workEndTime')" /> |
| | | </el-form-item> |
| | | <el-form-item label="弹性上班" |
| | | prop="flexibleStart" |
| | | v-if="currentType === 'worktime'"> |
| | | <el-switch v-model="form.flexibleStart" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="弹性时间(分钟)" prop="flexibleMinutes" v-if="currentType === 'worktime' && form.flexibleStart"> |
| | | <el-input-number v-model="form.flexibleMinutes" :min="0" :max="120" style="width: 100%" /> |
| | | <el-form-item label="弹性时间(分钟)" |
| | | prop="flexibleMinutes" |
| | | v-if="currentType === 'worktime' && form.flexibleStart"> |
| | | <el-input-number v-model="form.flexibleMinutes" |
| | | :min="0" |
| | | :max="120" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="状态" prop="status"> |
| | | <el-radio-group v-model="form.status"> |
| | | <el-radio value="active">启用</el-radio> |
| | | <el-radio value="inactive">停用</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | <el-form-item label="状态" |
| | | prop="status"> |
| | | <el-radio-group v-model="form.status"> |
| | | <el-radio value="active">启用</el-radio> |
| | | <el-radio value="inactive">停用</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="submitForm">确定</el-button> |
| | | <el-button @click="dialogVisible = false">取消</el-button> |
| | | <el-button type="primary" @click="submitForm">确定</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, onUnmounted } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { listHolidaySettings, addHolidaySettings, updateHolidaySettings, delHolidaySettings, listAnnualLeaveSettingList, addAnnualLeaveSetting, updateAnnualLeaveSetting, delAnnualLeaveSetting, listOvertimeSettingList, addOvertimeSetting, updateOvertimeSetting, delOvertimeSetting, listWorkingHoursSettingList, addWorkingHoursSetting, updateWorkingHoursSetting, delWorkingHoursSetting } from '@/api/collaborativeApproval/attendanceManagement.js' |
| | | import { ref, reactive, onMounted, onUnmounted } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import { |
| | | listHolidaySettings, |
| | | addHolidaySettings, |
| | | updateHolidaySettings, |
| | | delHolidaySettings, |
| | | listAnnualLeaveSettingList, |
| | | addAnnualLeaveSetting, |
| | | updateAnnualLeaveSetting, |
| | | delAnnualLeaveSetting, |
| | | listOvertimeSettingList, |
| | | addOvertimeSetting, |
| | | updateOvertimeSetting, |
| | | delOvertimeSetting, |
| | | listWorkingHoursSettingList, |
| | | addWorkingHoursSetting, |
| | | updateWorkingHoursSetting, |
| | | delWorkingHoursSetting, |
| | | } from "@/api/collaborativeApproval/attendanceManagement.js"; |
| | | |
| | | // 当前激活的标签页 |
| | | const activeTab = ref('holiday') |
| | | // 当前激活的标签页 |
| | | const activeTab = ref("holiday"); |
| | | |
| | | // 弹窗相关 |
| | | const dialogVisible = ref(false) |
| | | const dialogTitle = ref('') |
| | | const currentType = ref('') |
| | | const currentAction = ref('') |
| | | const currentEditId = ref('') |
| | | const formRef = ref() |
| | | const page = { |
| | | // 弹窗相关 |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref(""); |
| | | const currentType = ref(""); |
| | | const currentAction = ref(""); |
| | | const currentEditId = ref(""); |
| | | const formRef = ref(); |
| | | const page = { |
| | | current: 1, |
| | | size: 20, |
| | | total: 0, |
| | | } |
| | | const holidayData = ref([]) |
| | | const annualData = ref([]) |
| | | const overtimeData = ref([]) |
| | | const worktimeData = ref([]) |
| | | }; |
| | | const holidayData = ref([]); |
| | | const annualData = ref([]); |
| | | const overtimeData = ref([]); |
| | | const worktimeData = ref([]); |
| | | |
| | | // 打卡记录相关数据 |
| | | const attendanceData = ref([]) |
| | | const filteredAttendanceData = ref([]) |
| | | const attendanceDate = ref('') |
| | | const attendanceStatus = ref('') |
| | | // 打卡记录相关数据 |
| | | const attendanceData = ref([]); |
| | | const filteredAttendanceData = ref([]); |
| | | const attendanceDate = ref(""); |
| | | const attendanceStatus = ref(""); |
| | | |
| | | // 表单数据 |
| | | const form = reactive({ |
| | | name: '', |
| | | type: '', |
| | | dateRange: [], |
| | | startDate: '', |
| | | endDate: '', |
| | | days: 0, |
| | | employeeType: '', |
| | | workYears: '', |
| | | annualDays: 0, |
| | | maxCarryOver: 0, |
| | | startTime: '', // 加班开始时间 |
| | | endTime: '', // 加班结束时间 |
| | | workStartTime: '', // 上班时间 |
| | | workEndTime: '', // 下班时间 |
| | | rate: 1.5, |
| | | flexibleStart: false, |
| | | flexibleMinutes: 30, |
| | | status: 'active' |
| | | }) |
| | | |
| | | // 表单验证规则 |
| | | const rules = { |
| | | name: [{ required: true, message: '请输入名称', trigger: 'blur' }], |
| | | type: [{ required: true, message: '请选择类型', trigger: 'change' }], |
| | | dateRange: [{ required: true, message: '请选择日期范围', trigger: 'change' }], |
| | | days: [{ required: true, message: '请输入天数', trigger: 'blur' }], |
| | | employeeType: [{ required: true, message: '请选择员工类型', trigger: 'change' }], |
| | | workYears: [{ required: true, message: '请输入工作年限', trigger: 'blur' }], |
| | | annualDays: [{ required: true, message: '请输入年假天数', trigger: 'blur' }], |
| | | maxCarryOver: [{ required: true, message: '请输入最大结转天数', trigger: 'blur' }], |
| | | startTime: [{ |
| | | required: true, |
| | | message: '请选择开始时间', |
| | | trigger: 'change', |
| | | validator: (rule, value, callback) => { |
| | | if (!value) { |
| | | callback(new Error('请选择开始时间')) |
| | | } else { |
| | | callback() |
| | | } |
| | | } |
| | | }], |
| | | endTime: [{ |
| | | required: true, |
| | | message: '请选择结束时间', |
| | | trigger: 'change', |
| | | validator: (rule, value, callback) => { |
| | | if (!value) { |
| | | callback(new Error('请选择结束时间')) |
| | | } else { |
| | | callback() |
| | | } |
| | | } |
| | | }], |
| | | workStartTime: [{ |
| | | required: true, |
| | | message: '请选择上班时间', |
| | | trigger: 'change', |
| | | validator: (rule, value, callback) => { |
| | | if (!value) { |
| | | callback(new Error('请选择上班时间')) |
| | | } else { |
| | | callback() |
| | | } |
| | | } |
| | | }], |
| | | workEndTime: [{ |
| | | required: true, |
| | | message: '请选择下班时间', |
| | | trigger: 'change', |
| | | validator: (rule, value, callback) => { |
| | | if (!value) { |
| | | callback(new Error('请选择下班时间')) |
| | | } else { |
| | | callback() |
| | | } |
| | | } |
| | | }], |
| | | rate: [{ required: true, message: '请输入倍率', trigger: 'blur' }] |
| | | } |
| | | // 工具函数 |
| | | const getTagType = (type) => { |
| | | const tagMap = { |
| | | legal: 'success', adjustment: 'warning', special: 'info', company: 'primary', |
| | | weekday: 'primary', weekend: 'warning', holiday: 'danger', night: 'info', |
| | | regular: 'success', probation: 'info', intern: 'danger' |
| | | } |
| | | return tagMap[type] || 'info' |
| | | } |
| | | |
| | | const getTypeLabel = (type) => { |
| | | const labelMap = { |
| | | legal: '法定节假日', adjustment: '调休日', special: '特殊假期', company: '公司假期', |
| | | weekday: '工作日加班', weekend: '周末加班', holiday: '节假日加班', night: '深夜加班', |
| | | regular: '正式员工', probation: '试用期员工', intern: '实习生' |
| | | } |
| | | return labelMap[type] || type |
| | | } |
| | | |
| | | // 打卡记录相关工具函数 |
| | | const getAttendanceTagType = (status) => { |
| | | const tagMap = { |
| | | normal: 'success', |
| | | late: 'warning', |
| | | early: 'warning', |
| | | absent: 'danger' |
| | | } |
| | | return tagMap[status] || 'info' |
| | | } |
| | | |
| | | const getAttendanceStatusLabel = (status) => { |
| | | const labelMap = { |
| | | normal: '正常', |
| | | late: '迟到', |
| | | early: '早退', |
| | | absent: '缺勤' |
| | | } |
| | | return labelMap[status] || status |
| | | } |
| | | |
| | | const getTypeOptions = () => { |
| | | if (currentType.value === 'holiday') { |
| | | return [ |
| | | { label: '法定节假日', value: 'legal' }, |
| | | { label: '调休日', value: 'adjustment' }, |
| | | { label: '特殊假期', value: 'special' }, |
| | | { label: '公司假期', value: 'company' } |
| | | ] |
| | | } else if (currentType.value === 'overtime') { |
| | | return [ |
| | | { label: '工作日加班', value: 'weekday' }, |
| | | { label: '周末加班', value: 'weekend' }, |
| | | { label: '节假日加班', value: 'holiday' }, |
| | | { label: '深夜加班', value: 'night' } |
| | | ] |
| | | } else if (currentType.value === 'annual') { |
| | | return [ |
| | | { label: '正式员工', value: 'regular' }, |
| | | { label: '试用期员工', value: 'probation' }, |
| | | { label: '实习生', value: 'intern' } |
| | | ] |
| | | } |
| | | return [] |
| | | } |
| | | |
| | | // 计算假期天数 |
| | | const calculateDays = () => { |
| | | try { |
| | | if (form.dateRange && form.dateRange.length === 2 && form.dateRange[0] && form.dateRange[1]) { |
| | | const start = new Date(form.dateRange[0]) |
| | | const end = new Date(form.dateRange[1]) |
| | | form.startDate = start.toISOString().split('T')[0] |
| | | form.endDate = end.toISOString().split('T')[0] |
| | | |
| | | if (isNaN(start.getTime()) || isNaN(end.getTime())) { |
| | | console.warn('无效的日期格式') |
| | | return |
| | | } |
| | | |
| | | const diffTime = Math.abs(end - start) |
| | | const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1 |
| | | form.days = diffDays |
| | | } |
| | | } catch (error) { |
| | | console.error('计算天数失败:', error) |
| | | } |
| | | } |
| | | |
| | | // 验证时间格式 |
| | | // const validateTime = (time) => { |
| | | // if (!time) return '' |
| | | // if (typeof time === 'string') return time |
| | | // if (time instanceof Date) { |
| | | // return time.toTimeString().slice(0, 5) |
| | | // } |
| | | // return '' |
| | | // } |
| | | |
| | | // 验证时间字段 |
| | | const validateTimeField = (fieldName) => { |
| | | try { |
| | | const value = form[fieldName] |
| | | if (value && typeof value === 'object' && value.hour !== undefined) { |
| | | // 如果是时间对象,转换为字符串格式 |
| | | const hours = value.hour.toString().padStart(2, '0') |
| | | const minutes = value.minute.toString().padStart(2, '0') |
| | | form[fieldName] = `${hours}:${minutes}` |
| | | } |
| | | } catch (error) { |
| | | console.error(`验证时间字段 ${fieldName} 失败:`, error) |
| | | form[fieldName] = '' |
| | | } |
| | | } |
| | | |
| | | // 打开弹窗 |
| | | const openDialog = (type, action, row = null) => { |
| | | try { |
| | | currentType.value = type |
| | | currentAction.value = action |
| | | |
| | | if (action === 'add') { |
| | | dialogTitle.value = `新增${getTypeName(type)}` |
| | | currentEditId.value = '' |
| | | resetForm() |
| | | } else if (action === 'edit' && row) { |
| | | dialogTitle.value = `编辑${getTypeName(type)}` |
| | | currentEditId.value = row.id |
| | | fillForm(row) |
| | | } |
| | | |
| | | dialogVisible.value = true |
| | | } catch (error) { |
| | | console.error('打开弹窗失败:', error) |
| | | ElMessage.error('打开弹窗失败,请重试') |
| | | } |
| | | } |
| | | |
| | | const getTypeName = (type) => { |
| | | const nameMap = { |
| | | holiday: '假期', |
| | | annual: '年假规则', |
| | | overtime: '加班规则', |
| | | worktime: '时间段' |
| | | } |
| | | return nameMap[type] || '' |
| | | } |
| | | |
| | | const resetForm = () => { |
| | | Object.assign(form, { |
| | | name: '', |
| | | type: '', |
| | | // 表单数据 |
| | | const form = reactive({ |
| | | name: "", |
| | | type: "", |
| | | dateRange: [], |
| | | startDate: '', |
| | | endDate: '', |
| | | startDate: "", |
| | | endDate: "", |
| | | days: 0, |
| | | employeeType: '', |
| | | workYears: '', |
| | | employeeType: "", |
| | | workYears: "", |
| | | annualDays: 0, |
| | | maxCarryOver: 0, |
| | | startTime: '', |
| | | endTime: '', |
| | | workStartTime: '', |
| | | workEndTime: '', |
| | | startTime: "", // 加班开始时间 |
| | | endTime: "", // 加班结束时间 |
| | | workStartTime: "", // 上班时间 |
| | | workEndTime: "", // 下班时间 |
| | | rate: 1.5, |
| | | flexibleStart: false, |
| | | flexibleMinutes: 30, |
| | | status: 'active' |
| | | }) |
| | | } |
| | | status: "active", |
| | | }); |
| | | |
| | | const fillForm = (row) => { |
| | | if (currentType.value === 'holiday') { |
| | | Object.assign(form, { |
| | | name: row.name, |
| | | type: row.type, |
| | | dateRange: [new Date(row.startDate), new Date(row.endDate)], |
| | | startDate: row.startDate, |
| | | endDate: row.endDate, |
| | | days: row.days, |
| | | status: row.status |
| | | }) |
| | | } else if (currentType.value === 'annual') { |
| | | Object.assign(form, { |
| | | employeeType: row.employeeType, |
| | | workYears: row.workYears, |
| | | annualDays: row.annualDays, |
| | | maxCarryOver: row.maxCarryOver, |
| | | status: row.status |
| | | }) |
| | | } else if (currentType.value === 'overtime') { |
| | | Object.assign(form, { |
| | | name: row.name, |
| | | type: row.type, |
| | | startTime: row.startTime || '', |
| | | endTime: row.endTime || '', |
| | | rate: row.rate, |
| | | status: row.status |
| | | }) |
| | | } else if (currentType.value === 'worktime') { |
| | | Object.assign(form, { |
| | | name: row.name, |
| | | workStartTime: row.startTime || '', |
| | | workEndTime: row.endTime || '', |
| | | flexibleStart: row.flexibleStart, |
| | | flexibleMinutes: row.flexibleMinutes, |
| | | status: row.status |
| | | }) |
| | | } |
| | | } |
| | | // 表单验证规则 |
| | | const rules = { |
| | | name: [{ required: true, message: "请输入名称", trigger: "blur" }], |
| | | type: [{ required: true, message: "请选择类型", trigger: "change" }], |
| | | dateRange: [{ required: true, message: "请选择日期范围", trigger: "change" }], |
| | | days: [{ required: true, message: "请输入天数", trigger: "blur" }], |
| | | employeeType: [ |
| | | { required: true, message: "请选择员工类型", trigger: "change" }, |
| | | ], |
| | | workYears: [{ required: true, message: "请输入工作年限", trigger: "blur" }], |
| | | annualDays: [{ required: true, message: "请输入年假天数", trigger: "blur" }], |
| | | maxCarryOver: [ |
| | | { required: true, message: "请输入最大结转天数", trigger: "blur" }, |
| | | ], |
| | | startTime: [ |
| | | { |
| | | required: true, |
| | | message: "请选择开始时间", |
| | | trigger: "change", |
| | | validator: (rule, value, callback) => { |
| | | if (!value) { |
| | | callback(new Error("请选择开始时间")); |
| | | } else { |
| | | callback(); |
| | | } |
| | | }, |
| | | }, |
| | | ], |
| | | endTime: [ |
| | | { |
| | | required: true, |
| | | message: "请选择结束时间", |
| | | trigger: "change", |
| | | validator: (rule, value, callback) => { |
| | | if (!value) { |
| | | callback(new Error("请选择结束时间")); |
| | | } else { |
| | | callback(); |
| | | } |
| | | }, |
| | | }, |
| | | ], |
| | | workStartTime: [ |
| | | { |
| | | required: true, |
| | | message: "请选择上班时间", |
| | | trigger: "change", |
| | | validator: (rule, value, callback) => { |
| | | if (!value) { |
| | | callback(new Error("请选择上班时间")); |
| | | } else { |
| | | callback(); |
| | | } |
| | | }, |
| | | }, |
| | | ], |
| | | workEndTime: [ |
| | | { |
| | | required: true, |
| | | message: "请选择下班时间", |
| | | trigger: "change", |
| | | validator: (rule, value, callback) => { |
| | | if (!value) { |
| | | callback(new Error("请选择下班时间")); |
| | | } else { |
| | | callback(); |
| | | } |
| | | }, |
| | | }, |
| | | ], |
| | | rate: [{ required: true, message: "请输入倍率", trigger: "blur" }], |
| | | }; |
| | | // 工具函数 |
| | | const getTagType = type => { |
| | | const tagMap = { |
| | | legal: "success", |
| | | adjustment: "warning", |
| | | special: "info", |
| | | company: "primary", |
| | | weekday: "primary", |
| | | weekend: "warning", |
| | | holiday: "danger", |
| | | night: "info", |
| | | regular: "success", |
| | | probation: "info", |
| | | intern: "danger", |
| | | }; |
| | | return tagMap[type] || "info"; |
| | | }; |
| | | |
| | | // 提交表单 |
| | | const submitForm = async () => { |
| | | try { |
| | | if (!formRef.value) { |
| | | ElMessage.error('表单引用不存在') |
| | | return |
| | | const getTypeLabel = type => { |
| | | const labelMap = { |
| | | legal: "法定节假日", |
| | | adjustment: "调休日", |
| | | special: "特殊假期", |
| | | company: "公司假期", |
| | | weekday: "工作日加班", |
| | | weekend: "周末加班", |
| | | holiday: "节假日加班", |
| | | night: "深夜加班", |
| | | regular: "正式员工", |
| | | probation: "试用期员工", |
| | | intern: "实习生", |
| | | }; |
| | | return labelMap[type] || type; |
| | | }; |
| | | |
| | | // 打卡记录相关工具函数 |
| | | const getAttendanceTagType = status => { |
| | | const tagMap = { |
| | | normal: "success", |
| | | late: "warning", |
| | | early: "warning", |
| | | absent: "danger", |
| | | }; |
| | | return tagMap[status] || "info"; |
| | | }; |
| | | |
| | | const getAttendanceStatusLabel = status => { |
| | | const labelMap = { |
| | | normal: "正常", |
| | | late: "迟到", |
| | | early: "早退", |
| | | absent: "缺勤", |
| | | }; |
| | | return labelMap[status] || status; |
| | | }; |
| | | |
| | | const getTypeOptions = () => { |
| | | if (currentType.value === "holiday") { |
| | | return [ |
| | | { label: "法定节假日", value: "legal" }, |
| | | { label: "调休日", value: "adjustment" }, |
| | | { label: "特殊假期", value: "special" }, |
| | | { label: "公司假期", value: "company" }, |
| | | ]; |
| | | } else if (currentType.value === "overtime") { |
| | | return [ |
| | | { label: "工作日加班", value: "weekday" }, |
| | | { label: "周末加班", value: "weekend" }, |
| | | { label: "节假日加班", value: "holiday" }, |
| | | { label: "深夜加班", value: "night" }, |
| | | ]; |
| | | } else if (currentType.value === "annual") { |
| | | return [ |
| | | { label: "正式员工", value: "regular" }, |
| | | { label: "试用期员工", value: "probation" }, |
| | | { label: "实习生", value: "intern" }, |
| | | ]; |
| | | } |
| | | return []; |
| | | }; |
| | | |
| | | await formRef.value.validate() |
| | | // 计算假期天数 |
| | | const calculateDays = () => { |
| | | try { |
| | | if ( |
| | | form.dateRange && |
| | | form.dateRange.length === 2 && |
| | | form.dateRange[0] && |
| | | form.dateRange[1] |
| | | ) { |
| | | const start = new Date(form.dateRange[0]); |
| | | const end = new Date(form.dateRange[1]); |
| | | form.startDate = start.toISOString().split("T")[0]; |
| | | form.endDate = end.toISOString().split("T")[0]; |
| | | |
| | | if (currentAction.value === 'add') { |
| | | addItem() |
| | | } else if (currentAction.value === 'edit') { |
| | | editItem() |
| | | } |
| | | |
| | | dialogVisible.value = false |
| | | ElMessage.success('操作成功') |
| | | } catch (error) { |
| | | console.error('表单验证失败:', error) |
| | | ElMessage.error('表单验证失败,请检查输入') |
| | | } |
| | | } |
| | | |
| | | const addItem = () => { |
| | | |
| | | if (currentType.value === 'holiday') { |
| | | const params = { |
| | | name: form.name, |
| | | type: form.type, |
| | | startDate: form.startDate, |
| | | endDate: form.endDate, |
| | | days: form.days, |
| | | status: form.status |
| | | } |
| | | addHolidaySettings(params).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("添加成功"); |
| | | // dialogVisible.value = false; |
| | | getHolidaySettingsList() |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | } else if (currentType.value === 'annual') { |
| | | // annualData.value.push(newItem) |
| | | const params = { |
| | | employeeType: form.employeeType, |
| | | workYears: form.workYears, |
| | | annualDays: form.annualDays, |
| | | maxCarryOver: form.maxCarryOver, |
| | | status: form.status |
| | | } |
| | | addAnnualLeaveSetting(params).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("添加成功"); |
| | | // dialogVisible.value = false; |
| | | getAnnualLeaveSettingList() |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | } else if (currentType.value === 'overtime') { |
| | | const params = { |
| | | name: form.name, |
| | | type: form.type, |
| | | startTime: form.startTime || '', |
| | | endTime: form.endTime || '', |
| | | rate: form.rate, |
| | | status: form.status |
| | | } |
| | | addOvertimeSetting(params).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("添加成功"); |
| | | // dialogVisible.value = false; |
| | | getOvertimeSettingList() |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | // newItem.startTime = form.startTime || '' |
| | | // newItem.endTime = form.endTime || '' |
| | | // overtimeData.value.push(newItem) |
| | | } else if (currentType.value === 'worktime') { |
| | | const params = { |
| | | name: form.name, |
| | | startTime: form.workStartTime || '', |
| | | endTime: form.workEndTime || '', |
| | | flexibleStart: form.flexibleStart, |
| | | flexibleMinutes: form.flexibleMinutes, |
| | | status: form.status |
| | | } |
| | | addWorkingHoursSetting(params).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("添加成功"); |
| | | getWorkingHoursSettingList() |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | // newItem.startTime = form.workStartTime || '' |
| | | // newItem.endTime = form.workEndTime || '' |
| | | // worktimeData.value.push(newItem) |
| | | } |
| | | } |
| | | |
| | | const editItem = () => { |
| | | let dataArray |
| | | let index |
| | | |
| | | if (currentType.value === 'holiday') { |
| | | const params = { |
| | | id: currentEditId.value, |
| | | name: form.name, |
| | | type: form.type, |
| | | startDate: form.dateRange[0].toISOString().split('T')[0], |
| | | endDate: form.dateRange[1].toISOString().split('T')[0], |
| | | days: form.days, |
| | | status: form.status |
| | | } |
| | | updateHolidaySettings(params).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("更新成功"); |
| | | // dialogVisible.value = false; |
| | | getHolidaySettingsList() |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | } else if (currentType.value === 'annual') { |
| | | const params = { |
| | | id: currentEditId.value, |
| | | employeeType: form.employeeType, |
| | | workYears: form.workYears, |
| | | annualDays: form.annualDays, |
| | | maxCarryOver: form.maxCarryOver, |
| | | status: form.status |
| | | } |
| | | updateAnnualLeaveSetting(params).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("更新成功"); |
| | | getAnnualLeaveSettingList() |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | } else if (currentType.value === 'overtime') { |
| | | const params = { |
| | | id: currentEditId.value, |
| | | name: form.name, |
| | | type: form.type, |
| | | startTime: form.startTime || '', |
| | | endTime: form.endTime || '', |
| | | rate: form.rate, |
| | | status: form.status |
| | | } |
| | | updateOvertimeSetting(params).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("更新成功"); |
| | | getOvertimeSettingList() |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | |
| | | // dataArray = overtimeData.value |
| | | // index = dataArray.findIndex(item => item.id === currentEditId.value) |
| | | // if (index > -1) { |
| | | // dataArray[index] = { |
| | | // ...dataArray[index], |
| | | // name: form.name, |
| | | // type: form.type, |
| | | // startTime: form.startTime || '', |
| | | // endTime: form.endTime || '', |
| | | // rate: form.rate, |
| | | // status: form.status |
| | | // } |
| | | // } |
| | | } else if (currentType.value === 'worktime') { |
| | | const params = { |
| | | id: currentEditId.value, |
| | | name: form.name, |
| | | startTime: form.workStartTime || '', |
| | | endTime: form.workEndTime || '', |
| | | flexibleStart: form.flexibleStart, |
| | | flexibleMinutes: form.flexibleMinutes, |
| | | status: form.status |
| | | } |
| | | updateWorkingHoursSetting(params).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("更新成功"); |
| | | getWorkingHoursSettingList() |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | // dataArray = worktimeData.value |
| | | // index = dataArray.findIndex(item => item.id === currentEditId.value) |
| | | // if (index > -1) { |
| | | // dataArray[index] = { |
| | | // ...dataArray[index], |
| | | // name: form.name, |
| | | // startTime: form.workStartTime || '', |
| | | // endTime: form.workEndTime || '', |
| | | // flexibleStart: form.flexibleStart, |
| | | // flexibleMinutes: form.flexibleMinutes, |
| | | // status: form.status |
| | | // } |
| | | // } |
| | | } |
| | | } |
| | | |
| | | // 打卡记录过滤功能 |
| | | const filterAttendanceData = () => { |
| | | let filtered = attendanceData.value |
| | | |
| | | // 按日期过滤 |
| | | if (attendanceDate.value) { |
| | | filtered = filtered.filter(item => item.date === attendanceDate.value) |
| | | } |
| | | |
| | | // 按状态过滤 |
| | | if (attendanceStatus.value) { |
| | | filtered = filtered.filter(item => item.status === attendanceStatus.value) |
| | | } |
| | | |
| | | filteredAttendanceData.value = filtered |
| | | } |
| | | |
| | | // 导出打卡记录 |
| | | const exportAttendance = () => { |
| | | ElMessage.success('导出功能开发中...') |
| | | } |
| | | |
| | | // 初始化打卡记录假数据 |
| | | const initAttendanceData = () => { |
| | | const mockData = [ |
| | | { |
| | | id: 1, |
| | | employeeName: '陈志强', |
| | | department: '技术部', |
| | | date: '2025-08-15', |
| | | clockInTime: '09:00:00', |
| | | clockOutTime: '18:00:00', |
| | | workHours: '8.0h', |
| | | status: 'normal', |
| | | location: '公司总部', |
| | | remark: '' |
| | | }, |
| | | { |
| | | id: 2, |
| | | employeeName: '李雪梅', |
| | | department: '市场部', |
| | | date: '2025-08-16', |
| | | clockInTime: '08:58:00', |
| | | clockOutTime: '18:05:00', |
| | | workHours: '8.12h', |
| | | status: 'normal', |
| | | location: '公司总部', |
| | | remark: '' |
| | | }, |
| | | { |
| | | id: 3, |
| | | employeeName: '王建华', |
| | | department: '人事部', |
| | | date: '2025-08-16', |
| | | clockInTime: '09:02:00', |
| | | clockOutTime: '18:00:00', |
| | | workHours: '7.97h', |
| | | status: 'normal', |
| | | location: '公司总部', |
| | | remark: '' |
| | | }, |
| | | { |
| | | id: 4, |
| | | employeeName: '赵晓丽', |
| | | department: '财务部', |
| | | date: '2025-09-02', |
| | | clockInTime: '08:55:00', |
| | | clockOutTime: '18:10:00', |
| | | workHours: '8.25h', |
| | | status: 'normal', |
| | | location: '公司总部', |
| | | remark: '' |
| | | }, |
| | | { |
| | | id: 5, |
| | | employeeName: '张国庆', |
| | | department: '技术部', |
| | | date: '2025-09-02', |
| | | clockInTime: '09:00:00', |
| | | clockOutTime: '18:30:00', |
| | | workHours: '8.5h', |
| | | status: 'normal', |
| | | location: '公司总部', |
| | | remark: '加班' |
| | | }, |
| | | { |
| | | id: 6, |
| | | employeeName: '刘明辉', |
| | | department: '运营部', |
| | | date: '2025-09-03', |
| | | clockInTime: '09:05:00', |
| | | clockOutTime: '18:00:00', |
| | | workHours: '7.92h', |
| | | status: 'normal', |
| | | location: '公司总部', |
| | | remark: '' |
| | | }, |
| | | { |
| | | id: 7, |
| | | employeeName: '孙丽华', |
| | | department: '设计部', |
| | | date: '2025-09-03', |
| | | clockInTime: '08:59:00', |
| | | clockOutTime: '18:02:00', |
| | | workHours: '8.05h', |
| | | status: 'normal', |
| | | location: '公司总部', |
| | | remark: '' |
| | | }, |
| | | { |
| | | id: 8, |
| | | employeeName: '周建军', |
| | | department: '销售部', |
| | | date: '2025-09-04', |
| | | clockInTime: '09:15:00', |
| | | clockOutTime: '18:00:00', |
| | | workHours: '7.75h', |
| | | status: 'late', |
| | | location: '公司总部', |
| | | remark: '交通堵塞' |
| | | }, |
| | | { |
| | | id: 9, |
| | | employeeName: '吴小芳', |
| | | department: '客服部', |
| | | date: '2025-09-04', |
| | | clockInTime: '09:01:00', |
| | | clockOutTime: '18:00:00', |
| | | workHours: '7.98h', |
| | | status: 'normal', |
| | | location: '公司总部', |
| | | remark: '' |
| | | }, |
| | | { |
| | | id: 10, |
| | | employeeName: '马文杰', |
| | | department: '技术部', |
| | | date: '2025-09-05', |
| | | clockInTime: '08:57:00', |
| | | clockOutTime: '17:30:00', |
| | | workHours: '7.55h', |
| | | status: 'early', |
| | | location: '公司总部', |
| | | remark: '有急事提前离开' |
| | | }, |
| | | { |
| | | id: 11, |
| | | employeeName: '林晓东', |
| | | department: '行政部', |
| | | date: '2025-09-05', |
| | | clockInTime: '09:03:00', |
| | | clockOutTime: '18:08:00', |
| | | workHours: '8.08h', |
| | | status: 'normal', |
| | | location: '公司总部', |
| | | remark: '' |
| | | }, |
| | | { |
| | | id: 12, |
| | | employeeName: '黄美玲', |
| | | department: '财务部', |
| | | date: '2025-09-06', |
| | | clockInTime: '', |
| | | clockOutTime: '', |
| | | workHours: '0h', |
| | | status: 'absent', |
| | | location: '', |
| | | remark: '请病假' |
| | | }, |
| | | { |
| | | id: 13, |
| | | employeeName: '郑海涛', |
| | | department: '市场部', |
| | | date: '2025-08-14', |
| | | clockInTime: '09:00:00', |
| | | clockOutTime: '18:00:00', |
| | | workHours: '8.0h', |
| | | status: 'normal', |
| | | location: '公司总部', |
| | | remark: '' |
| | | }, |
| | | { |
| | | id: 14, |
| | | employeeName: '谢丽娟', |
| | | department: '人事部', |
| | | date: '2025-08-20', |
| | | clockInTime: '08:58:00', |
| | | clockOutTime: '18:03:00', |
| | | workHours: '8.08h', |
| | | status: 'normal', |
| | | location: '公司总部', |
| | | remark: '' |
| | | }, |
| | | { |
| | | id: 15, |
| | | employeeName: '何志伟', |
| | | department: '技术部', |
| | | date: '2025-08-21', |
| | | clockInTime: '09:10:00', |
| | | clockOutTime: '18:00:00', |
| | | workHours: '7.83h', |
| | | status: 'late', |
| | | location: '公司总部', |
| | | remark: '' |
| | | }, |
| | | { |
| | | id: 16, |
| | | employeeName: '许雅芳', |
| | | department: '设计部', |
| | | date: '2025-08-22', |
| | | clockInTime: '09:01:00', |
| | | clockOutTime: '18:00:00', |
| | | workHours: '7.98h', |
| | | status: 'normal', |
| | | location: '公司总部', |
| | | remark: '' |
| | | }, |
| | | { |
| | | id: 17, |
| | | employeeName: '邓建平', |
| | | department: '运营部', |
| | | date: '2025-09-10', |
| | | clockInTime: '08:59:00', |
| | | clockOutTime: '18:05:00', |
| | | workHours: '8.1h', |
| | | status: 'normal', |
| | | location: '公司总部', |
| | | remark: '' |
| | | }, |
| | | { |
| | | id: 18, |
| | | employeeName: '曾小红', |
| | | department: '客服部', |
| | | date: '2025-09-11', |
| | | clockInTime: '09:02:00', |
| | | clockOutTime: '18:00:00', |
| | | workHours: '7.97h', |
| | | status: 'normal', |
| | | location: '公司总部', |
| | | remark: '' |
| | | } |
| | | ] |
| | | |
| | | attendanceData.value = mockData |
| | | filteredAttendanceData.value = mockData |
| | | } |
| | | |
| | | // 删除项目 |
| | | const deleteItem = (type, row) => { |
| | | ElMessageBox.confirm('确定要删除这个项目吗?', '提示', { |
| | | confirmButtonText: '确定', |
| | | cancelButtonText: '取消', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | let ids = []; |
| | | let dataArray |
| | | if (type === 'holiday') { |
| | | ids.push(row.id) |
| | | delHolidaySettings(ids).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("删除成功"); |
| | | ids = [] |
| | | getHolidaySettingsList() |
| | | if (isNaN(start.getTime()) || isNaN(end.getTime())) { |
| | | console.warn("无效的日期格式"); |
| | | return; |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | |
| | | const diffTime = Math.abs(end - start); |
| | | const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1; |
| | | form.days = diffDays; |
| | | } |
| | | } catch (error) { |
| | | console.error("计算天数失败:", error); |
| | | } |
| | | else if (type === 'annual') { |
| | | ids.push(row.id) |
| | | delAnnualLeaveSetting(ids).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("删除成功"); |
| | | ids = [] |
| | | getAnnualLeaveSettingList() |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | }; |
| | | |
| | | // 验证时间格式 |
| | | // const validateTime = (time) => { |
| | | // if (!time) return '' |
| | | // if (typeof time === 'string') return time |
| | | // if (time instanceof Date) { |
| | | // return time.toTimeString().slice(0, 5) |
| | | // } |
| | | // return '' |
| | | // } |
| | | |
| | | // 验证时间字段 |
| | | const validateTimeField = fieldName => { |
| | | try { |
| | | const value = form[fieldName]; |
| | | if (value && typeof value === "object" && value.hour !== undefined) { |
| | | // 如果是时间对象,转换为字符串格式 |
| | | const hours = value.hour.toString().padStart(2, "0"); |
| | | const minutes = value.minute.toString().padStart(2, "0"); |
| | | form[fieldName] = `${hours}:${minutes}`; |
| | | } |
| | | } catch (error) { |
| | | console.error(`验证时间字段 ${fieldName} 失败:`, error); |
| | | form[fieldName] = ""; |
| | | } |
| | | else if (type === 'overtime') { |
| | | ids.push(row.id) |
| | | delOvertimeSetting(ids).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("删除成功"); |
| | | ids = [] |
| | | getOvertimeSettingList() |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | }; |
| | | |
| | | // 打开弹窗 |
| | | const openDialog = (type, action, row = null) => { |
| | | try { |
| | | currentType.value = type; |
| | | currentAction.value = action; |
| | | |
| | | if (action === "add") { |
| | | dialogTitle.value = `新增${getTypeName(type)}`; |
| | | currentEditId.value = ""; |
| | | resetForm(); |
| | | } else if (action === "edit" && row) { |
| | | dialogTitle.value = `编辑${getTypeName(type)}`; |
| | | currentEditId.value = row.id; |
| | | fillForm(row); |
| | | } |
| | | |
| | | dialogVisible.value = true; |
| | | } catch (error) { |
| | | console.error("打开弹窗失败:", error); |
| | | ElMessage.error("打开弹窗失败,请重试"); |
| | | } |
| | | else if (type === 'worktime') { |
| | | ids.push(row.id) |
| | | delWorkingHoursSetting(ids).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("删除成功"); |
| | | ids = [] |
| | | getWorkingHoursSettingList() |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | }; |
| | | |
| | | const getTypeName = type => { |
| | | const nameMap = { |
| | | holiday: "假期", |
| | | annual: "年假规则", |
| | | overtime: "加班规则", |
| | | worktime: "时间段", |
| | | }; |
| | | return nameMap[type] || ""; |
| | | }; |
| | | |
| | | const resetForm = () => { |
| | | Object.assign(form, { |
| | | name: "", |
| | | type: "", |
| | | dateRange: [], |
| | | startDate: "", |
| | | endDate: "", |
| | | days: 0, |
| | | employeeType: "", |
| | | workYears: "", |
| | | annualDays: 0, |
| | | maxCarryOver: 0, |
| | | startTime: "", |
| | | endTime: "", |
| | | workStartTime: "", |
| | | workEndTime: "", |
| | | rate: 1.5, |
| | | flexibleStart: false, |
| | | flexibleMinutes: 30, |
| | | status: "active", |
| | | }); |
| | | }; |
| | | |
| | | const fillForm = row => { |
| | | if (currentType.value === "holiday") { |
| | | Object.assign(form, { |
| | | name: row.name, |
| | | type: row.type, |
| | | dateRange: [new Date(row.startDate), new Date(row.endDate)], |
| | | startDate: row.startDate, |
| | | endDate: row.endDate, |
| | | days: row.days, |
| | | status: row.status, |
| | | }); |
| | | } else if (currentType.value === "annual") { |
| | | Object.assign(form, { |
| | | employeeType: row.employeeType, |
| | | workYears: row.workYears, |
| | | annualDays: row.annualDays, |
| | | maxCarryOver: row.maxCarryOver, |
| | | status: row.status, |
| | | }); |
| | | } else if (currentType.value === "overtime") { |
| | | Object.assign(form, { |
| | | name: row.name, |
| | | type: row.type, |
| | | startTime: row.startTime || "", |
| | | endTime: row.endTime || "", |
| | | rate: row.rate, |
| | | status: row.status, |
| | | }); |
| | | } else if (currentType.value === "worktime") { |
| | | Object.assign(form, { |
| | | name: row.name, |
| | | workStartTime: row.startTime || "", |
| | | workEndTime: row.endTime || "", |
| | | flexibleStart: row.flexibleStart, |
| | | flexibleMinutes: row.flexibleMinutes, |
| | | status: row.status, |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | // 提交表单 |
| | | const submitForm = async () => { |
| | | try { |
| | | if (!formRef.value) { |
| | | ElMessage.error("表单引用不存在"); |
| | | return; |
| | | } |
| | | |
| | | await formRef.value.validate(); |
| | | |
| | | if (currentAction.value === "add") { |
| | | addItem(); |
| | | } else if (currentAction.value === "edit") { |
| | | editItem(); |
| | | } |
| | | |
| | | dialogVisible.value = false; |
| | | ElMessage.success("操作成功"); |
| | | } catch (error) { |
| | | console.error("表单验证失败:", error); |
| | | ElMessage.error("表单验证失败,请检查输入"); |
| | | } |
| | | }; |
| | | |
| | | const addItem = () => { |
| | | if (currentType.value === "holiday") { |
| | | const params = { |
| | | name: form.name, |
| | | type: form.type, |
| | | startDate: form.startDate, |
| | | endDate: form.endDate, |
| | | days: form.days, |
| | | status: form.status, |
| | | }; |
| | | addHolidaySettings(params) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("添加成功"); |
| | | // dialogVisible.value = false; |
| | | getHolidaySettingsList(); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | } else if (currentType.value === "annual") { |
| | | // annualData.value.push(newItem) |
| | | const params = { |
| | | employeeType: form.employeeType, |
| | | workYears: form.workYears, |
| | | annualDays: form.annualDays, |
| | | maxCarryOver: form.maxCarryOver, |
| | | status: form.status, |
| | | }; |
| | | addAnnualLeaveSetting(params) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("添加成功"); |
| | | // dialogVisible.value = false; |
| | | getAnnualLeaveSettingList(); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | } else if (currentType.value === "overtime") { |
| | | const params = { |
| | | name: form.name, |
| | | type: form.type, |
| | | startTime: form.startTime || "", |
| | | endTime: form.endTime || "", |
| | | rate: form.rate, |
| | | status: form.status, |
| | | }; |
| | | addOvertimeSetting(params) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("添加成功"); |
| | | // dialogVisible.value = false; |
| | | getOvertimeSettingList(); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | // newItem.startTime = form.startTime || '' |
| | | // newItem.endTime = form.endTime || '' |
| | | // overtimeData.value.push(newItem) |
| | | } else if (currentType.value === "worktime") { |
| | | const params = { |
| | | name: form.name, |
| | | startTime: form.workStartTime || "", |
| | | endTime: form.workEndTime || "", |
| | | flexibleStart: form.flexibleStart, |
| | | flexibleMinutes: form.flexibleMinutes, |
| | | status: form.status, |
| | | }; |
| | | addWorkingHoursSetting(params) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("添加成功"); |
| | | getWorkingHoursSettingList(); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | // newItem.startTime = form.workStartTime || '' |
| | | // newItem.endTime = form.workEndTime || '' |
| | | // worktimeData.value.push(newItem) |
| | | } |
| | | }; |
| | | |
| | | const editItem = () => { |
| | | let dataArray; |
| | | let index; |
| | | |
| | | if (currentType.value === "holiday") { |
| | | const params = { |
| | | id: currentEditId.value, |
| | | name: form.name, |
| | | type: form.type, |
| | | startDate: form.dateRange[0].toISOString().split("T")[0], |
| | | endDate: form.dateRange[1].toISOString().split("T")[0], |
| | | days: form.days, |
| | | status: form.status, |
| | | }; |
| | | updateHolidaySettings(params) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("更新成功"); |
| | | // dialogVisible.value = false; |
| | | getHolidaySettingsList(); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | } else if (currentType.value === "annual") { |
| | | const params = { |
| | | id: currentEditId.value, |
| | | employeeType: form.employeeType, |
| | | workYears: form.workYears, |
| | | annualDays: form.annualDays, |
| | | maxCarryOver: form.maxCarryOver, |
| | | status: form.status, |
| | | }; |
| | | updateAnnualLeaveSetting(params) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("更新成功"); |
| | | getAnnualLeaveSettingList(); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | } else if (currentType.value === "overtime") { |
| | | const params = { |
| | | id: currentEditId.value, |
| | | name: form.name, |
| | | type: form.type, |
| | | startTime: form.startTime || "", |
| | | endTime: form.endTime || "", |
| | | rate: form.rate, |
| | | status: form.status, |
| | | }; |
| | | updateOvertimeSetting(params) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("更新成功"); |
| | | getOvertimeSettingList(); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | |
| | | // dataArray = overtimeData.value |
| | | // index = dataArray.findIndex(item => item.id === currentEditId.value) |
| | | // if (index > -1) { |
| | | // dataArray[index] = { |
| | | // ...dataArray[index], |
| | | // name: form.name, |
| | | // type: form.type, |
| | | // startTime: form.startTime || '', |
| | | // endTime: form.endTime || '', |
| | | // rate: form.rate, |
| | | // status: form.status |
| | | // } |
| | | // } |
| | | } else if (currentType.value === "worktime") { |
| | | const params = { |
| | | id: currentEditId.value, |
| | | name: form.name, |
| | | startTime: form.workStartTime || "", |
| | | endTime: form.workEndTime || "", |
| | | flexibleStart: form.flexibleStart, |
| | | flexibleMinutes: form.flexibleMinutes, |
| | | status: form.status, |
| | | }; |
| | | updateWorkingHoursSetting(params) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("更新成功"); |
| | | getWorkingHoursSettingList(); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | // dataArray = worktimeData.value |
| | | // index = dataArray.findIndex(item => item.id === currentEditId.value) |
| | | // if (index > -1) { |
| | | // dataArray[index] = { |
| | | // ...dataArray[index], |
| | | // name: form.name, |
| | | // startTime: form.workStartTime || '', |
| | | // endTime: form.workEndTime || '', |
| | | // flexibleStart: form.flexibleStart, |
| | | // flexibleMinutes: form.flexibleMinutes, |
| | | // status: form.status |
| | | // } |
| | | // } |
| | | } |
| | | }; |
| | | |
| | | // 打卡记录过滤功能 |
| | | const filterAttendanceData = () => { |
| | | let filtered = attendanceData.value; |
| | | |
| | | // 按日期过滤 |
| | | if (attendanceDate.value) { |
| | | filtered = filtered.filter(item => item.date === attendanceDate.value); |
| | | } |
| | | |
| | | // const index = dataArray.findIndex(item => item.id === row.id) |
| | | // if (index > -1) { |
| | | // dataArray.splice(index, 1) |
| | | // ElMessage.success('删除成功') |
| | | // } |
| | | }) |
| | | } |
| | | // 获取假期设置列表 |
| | | const getHolidaySettingsList = () => { |
| | | // tableLoading.value = true; |
| | | listHolidaySettings({...page.value}) |
| | | .then(res => { |
| | | // tableLoading.value = false; |
| | | holidayData.value = res.data.records |
| | | page.total = res.data.total; |
| | | }).catch(err => { |
| | | // tableLoading.value = false; |
| | | }) |
| | | }; |
| | | // 获取年假规则列表 |
| | | const getAnnualLeaveSettingList = () => { |
| | | // 按状态过滤 |
| | | if (attendanceStatus.value) { |
| | | filtered = filtered.filter(item => item.status === attendanceStatus.value); |
| | | } |
| | | |
| | | listAnnualLeaveSettingList({...page.value}) |
| | | .then(res => { |
| | | // console.log(res.data) |
| | | annualData.value = res.data.records |
| | | page.total = res.data.total; |
| | | }).catch(err => { |
| | | }) |
| | | }; |
| | | // 获取加班规则列表 |
| | | const getOvertimeSettingList = () => { |
| | | filteredAttendanceData.value = filtered; |
| | | }; |
| | | |
| | | listOvertimeSettingList({...page.value}) |
| | | .then(res => { |
| | | // console.log(res.data) |
| | | overtimeData.value = res.data.records |
| | | page.total = res.data.total; |
| | | }).catch(err => { |
| | | }) |
| | | }; |
| | | // 获取工作时间规则列表 |
| | | const getWorkingHoursSettingList = () => { |
| | | // 导出打卡记录 |
| | | const exportAttendance = () => { |
| | | ElMessage.success("导出功能开发中..."); |
| | | }; |
| | | |
| | | listWorkingHoursSettingList({...page.value}) |
| | | .then(res => { |
| | | // console.log(res.data) |
| | | worktimeData.value = res.data.records |
| | | page.total = res.data.total; |
| | | }).catch(err => { |
| | | }) |
| | | }; |
| | | onMounted(() => { |
| | | getHolidaySettingsList() |
| | | getAnnualLeaveSettingList() |
| | | getOvertimeSettingList() |
| | | getWorkingHoursSettingList() |
| | | initAttendanceData() |
| | | console.log('考勤管理页面加载完成') |
| | | }) |
| | | // 初始化打卡记录假数据 |
| | | const initAttendanceData = () => { |
| | | const mockData = [ |
| | | { |
| | | id: 1, |
| | | employeeName: "陈志强", |
| | | department: "技术部", |
| | | date: "2025-08-15", |
| | | clockInTime: "09:00:00", |
| | | clockOutTime: "18:00:00", |
| | | workHours: "8.0h", |
| | | status: "normal", |
| | | location: "公司总部", |
| | | remark: "", |
| | | }, |
| | | { |
| | | id: 2, |
| | | employeeName: "李雪梅", |
| | | department: "市场部", |
| | | date: "2025-08-16", |
| | | clockInTime: "08:58:00", |
| | | clockOutTime: "18:05:00", |
| | | workHours: "8.12h", |
| | | status: "normal", |
| | | location: "公司总部", |
| | | remark: "", |
| | | }, |
| | | { |
| | | id: 3, |
| | | employeeName: "王建华", |
| | | department: "人事部", |
| | | date: "2025-08-16", |
| | | clockInTime: "09:02:00", |
| | | clockOutTime: "18:00:00", |
| | | workHours: "7.97h", |
| | | status: "normal", |
| | | location: "公司总部", |
| | | remark: "", |
| | | }, |
| | | { |
| | | id: 4, |
| | | employeeName: "赵晓丽", |
| | | department: "财务部", |
| | | date: "2025-09-02", |
| | | clockInTime: "08:55:00", |
| | | clockOutTime: "18:10:00", |
| | | workHours: "8.25h", |
| | | status: "normal", |
| | | location: "公司总部", |
| | | remark: "", |
| | | }, |
| | | { |
| | | id: 5, |
| | | employeeName: "张国庆", |
| | | department: "技术部", |
| | | date: "2025-09-02", |
| | | clockInTime: "09:00:00", |
| | | clockOutTime: "18:30:00", |
| | | workHours: "8.5h", |
| | | status: "normal", |
| | | location: "公司总部", |
| | | remark: "加班", |
| | | }, |
| | | { |
| | | id: 6, |
| | | employeeName: "刘明辉", |
| | | department: "运营部", |
| | | date: "2025-09-03", |
| | | clockInTime: "09:05:00", |
| | | clockOutTime: "18:00:00", |
| | | workHours: "7.92h", |
| | | status: "normal", |
| | | location: "公司总部", |
| | | remark: "", |
| | | }, |
| | | { |
| | | id: 7, |
| | | employeeName: "孙丽华", |
| | | department: "设计部", |
| | | date: "2025-09-03", |
| | | clockInTime: "08:59:00", |
| | | clockOutTime: "18:02:00", |
| | | workHours: "8.05h", |
| | | status: "normal", |
| | | location: "公司总部", |
| | | remark: "", |
| | | }, |
| | | { |
| | | id: 8, |
| | | employeeName: "周建军", |
| | | department: "销售部", |
| | | date: "2025-09-04", |
| | | clockInTime: "09:15:00", |
| | | clockOutTime: "18:00:00", |
| | | workHours: "7.75h", |
| | | status: "late", |
| | | location: "公司总部", |
| | | remark: "交通堵塞", |
| | | }, |
| | | { |
| | | id: 9, |
| | | employeeName: "吴小芳", |
| | | department: "客服部", |
| | | date: "2025-09-04", |
| | | clockInTime: "09:01:00", |
| | | clockOutTime: "18:00:00", |
| | | workHours: "7.98h", |
| | | status: "normal", |
| | | location: "公司总部", |
| | | remark: "", |
| | | }, |
| | | { |
| | | id: 10, |
| | | employeeName: "马文杰", |
| | | department: "技术部", |
| | | date: "2025-09-05", |
| | | clockInTime: "08:57:00", |
| | | clockOutTime: "17:30:00", |
| | | workHours: "7.55h", |
| | | status: "early", |
| | | location: "公司总部", |
| | | remark: "有急事提前离开", |
| | | }, |
| | | { |
| | | id: 11, |
| | | employeeName: "林晓东", |
| | | department: "行政部", |
| | | date: "2025-09-05", |
| | | clockInTime: "09:03:00", |
| | | clockOutTime: "18:08:00", |
| | | workHours: "8.08h", |
| | | status: "normal", |
| | | location: "公司总部", |
| | | remark: "", |
| | | }, |
| | | { |
| | | id: 12, |
| | | employeeName: "黄美玲", |
| | | department: "财务部", |
| | | date: "2025-09-06", |
| | | clockInTime: "", |
| | | clockOutTime: "", |
| | | workHours: "0h", |
| | | status: "absent", |
| | | location: "", |
| | | remark: "请病假", |
| | | }, |
| | | { |
| | | id: 13, |
| | | employeeName: "郑海涛", |
| | | department: "市场部", |
| | | date: "2025-08-14", |
| | | clockInTime: "09:00:00", |
| | | clockOutTime: "18:00:00", |
| | | workHours: "8.0h", |
| | | status: "normal", |
| | | location: "公司总部", |
| | | remark: "", |
| | | }, |
| | | { |
| | | id: 14, |
| | | employeeName: "谢丽娟", |
| | | department: "人事部", |
| | | date: "2025-08-20", |
| | | clockInTime: "08:58:00", |
| | | clockOutTime: "18:03:00", |
| | | workHours: "8.08h", |
| | | status: "normal", |
| | | location: "公司总部", |
| | | remark: "", |
| | | }, |
| | | { |
| | | id: 15, |
| | | employeeName: "何志伟", |
| | | department: "技术部", |
| | | date: "2025-08-21", |
| | | clockInTime: "09:10:00", |
| | | clockOutTime: "18:00:00", |
| | | workHours: "7.83h", |
| | | status: "late", |
| | | location: "公司总部", |
| | | remark: "", |
| | | }, |
| | | { |
| | | id: 16, |
| | | employeeName: "许雅芳", |
| | | department: "设计部", |
| | | date: "2025-08-22", |
| | | clockInTime: "09:01:00", |
| | | clockOutTime: "18:00:00", |
| | | workHours: "7.98h", |
| | | status: "normal", |
| | | location: "公司总部", |
| | | remark: "", |
| | | }, |
| | | { |
| | | id: 17, |
| | | employeeName: "邓建平", |
| | | department: "运营部", |
| | | date: "2025-09-10", |
| | | clockInTime: "08:59:00", |
| | | clockOutTime: "18:05:00", |
| | | workHours: "8.1h", |
| | | status: "normal", |
| | | location: "公司总部", |
| | | remark: "", |
| | | }, |
| | | { |
| | | id: 18, |
| | | employeeName: "曾小红", |
| | | department: "客服部", |
| | | date: "2025-09-11", |
| | | clockInTime: "09:02:00", |
| | | clockOutTime: "18:00:00", |
| | | workHours: "7.97h", |
| | | status: "normal", |
| | | location: "公司总部", |
| | | remark: "", |
| | | }, |
| | | ]; |
| | | |
| | | onUnmounted(() => { |
| | | // 清理工作 |
| | | dialogVisible.value = false |
| | | currentType.value = '' |
| | | currentAction.value = '' |
| | | currentEditId.value = '' |
| | | }) |
| | | attendanceData.value = mockData; |
| | | filteredAttendanceData.value = mockData; |
| | | }; |
| | | |
| | | // 删除项目 |
| | | const deleteItem = (type, row) => { |
| | | ElMessageBox.confirm("确定要删除这个项目吗?", "提示", { |
| | | confirmButtonText: "确定", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }).then(() => { |
| | | let ids = []; |
| | | let dataArray; |
| | | if (type === "holiday") { |
| | | ids.push(row.id); |
| | | delHolidaySettings(ids) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("删除成功"); |
| | | ids = []; |
| | | getHolidaySettingsList(); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | } else if (type === "annual") { |
| | | ids.push(row.id); |
| | | delAnnualLeaveSetting(ids) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("删除成功"); |
| | | ids = []; |
| | | getAnnualLeaveSettingList(); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | } else if (type === "overtime") { |
| | | ids.push(row.id); |
| | | delOvertimeSetting(ids) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("删除成功"); |
| | | ids = []; |
| | | getOvertimeSettingList(); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | } else if (type === "worktime") { |
| | | ids.push(row.id); |
| | | delWorkingHoursSetting(ids) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("删除成功"); |
| | | ids = []; |
| | | getWorkingHoursSettingList(); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | } |
| | | |
| | | // const index = dataArray.findIndex(item => item.id === row.id) |
| | | // if (index > -1) { |
| | | // dataArray.splice(index, 1) |
| | | // ElMessage.success('删除成功') |
| | | // } |
| | | }); |
| | | }; |
| | | // 获取假期设置列表 |
| | | const getHolidaySettingsList = () => { |
| | | // tableLoading.value = true; |
| | | listHolidaySettings({ ...page.value }) |
| | | .then(res => { |
| | | // tableLoading.value = false; |
| | | holidayData.value = res.data.records; |
| | | page.total = res.data.total; |
| | | }) |
| | | .catch(err => { |
| | | // tableLoading.value = false; |
| | | }); |
| | | }; |
| | | // 获取年假规则列表 |
| | | const getAnnualLeaveSettingList = () => { |
| | | listAnnualLeaveSettingList({ ...page.value }) |
| | | .then(res => { |
| | | // console.log(res.data) |
| | | annualData.value = res.data.records; |
| | | page.total = res.data.total; |
| | | }) |
| | | .catch(err => {}); |
| | | }; |
| | | // 获取加班规则列表 |
| | | const getOvertimeSettingList = () => { |
| | | listOvertimeSettingList({ ...page.value }) |
| | | .then(res => { |
| | | // console.log(res.data) |
| | | overtimeData.value = res.data.records; |
| | | page.total = res.data.total; |
| | | }) |
| | | .catch(err => {}); |
| | | }; |
| | | // 获取工作时间规则列表 |
| | | const getWorkingHoursSettingList = () => { |
| | | listWorkingHoursSettingList({ ...page.value }) |
| | | .then(res => { |
| | | // console.log(res.data) |
| | | worktimeData.value = res.data.records; |
| | | page.total = res.data.total; |
| | | }) |
| | | .catch(err => {}); |
| | | }; |
| | | onMounted(() => { |
| | | getHolidaySettingsList(); |
| | | getAnnualLeaveSettingList(); |
| | | getOvertimeSettingList(); |
| | | getWorkingHoursSettingList(); |
| | | initAttendanceData(); |
| | | console.log("考勤管理页面加载完成"); |
| | | }); |
| | | |
| | | onUnmounted(() => { |
| | | // 清理工作 |
| | | dialogVisible.value = false; |
| | | currentType.value = ""; |
| | | currentAction.value = ""; |
| | | currentEditId.value = ""; |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .tab-content { |
| | | padding: 20px 0; |
| | | } |
| | | .tab-content { |
| | | padding: 20px 0; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | |
| | | :deep(.el-tabs__content) { |
| | | padding: 20px; |
| | | } |
| | | :deep(.el-tabs__content) { |
| | | padding: 20px; |
| | | } |
| | | |
| | | :deep(.el-form-item) { |
| | | margin-bottom: 20px; |
| | | } |
| | | :deep(.el-form-item) { |
| | | margin-bottom: 20px; |
| | | } |
| | | </style> |
| | |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">知识标题:</span> |
| | | <el-input |
| | | v-model="searchForm.title" |
| | | style="width: 240px" |
| | | placeholder="请输入知识标题搜索" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | /> |
| | | <el-input v-model="searchForm.title" |
| | | style="width: 240px" |
| | | placeholder="请输入知识标题搜索" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" /> |
| | | <span class="search_title ml10">知识类型:</span> |
| | | <el-select v-model="searchForm.type" clearable @change="handleQuery" style="width: 240px"> |
| | | <el-option label="合同特批" :value="'contract'" /> |
| | | <el-option label="审批案例" :value="'approval'" /> |
| | | <el-option label="解决方案" :value="'solution'" /> |
| | | <el-option label="经验总结" :value="'experience'" /> |
| | | <el-option label="操作指南" :value="'guide'" /> |
| | | <el-select v-model="searchForm.type" |
| | | clearable |
| | | @change="handleQuery" |
| | | style="width: 240px"> |
| | | <el-option label="合同特批" |
| | | :value="'contract'" /> |
| | | <el-option label="审批案例" |
| | | :value="'approval'" /> |
| | | <el-option label="解决方案" |
| | | :value="'solution'" /> |
| | | <el-option label="经验总结" |
| | | :value="'experience'" /> |
| | | <el-option label="操作指南" |
| | | :value="'guide'" /> |
| | | </el-select> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px"> |
| | | <el-button type="primary" |
| | | @click="handleQuery" |
| | | style="margin-left: 10px"> |
| | | 搜索 |
| | | </el-button> |
| | | </div> |
| | | <div> |
| | | <el-button @click="handleExport" style="margin-right: 10px">导出</el-button> |
| | | <el-button type="primary" @click="openForm('add')">新增知识</el-button> |
| | | <el-button type="danger" plain @click="handleDelete">删除</el-button> |
| | | <el-button @click="handleExport" |
| | | style="margin-right: 10px">导出</el-button> |
| | | <el-button type="primary" |
| | | @click="openForm('add')">新增知识</el-button> |
| | | <el-button type="danger" |
| | | plain |
| | | @click="handleDelete">删除</el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="table_list"> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total" |
| | | ></PIMTable> |
| | | <PIMTable rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total"></PIMTable> |
| | | </div> |
| | | |
| | | <!-- 新增/编辑知识弹窗 --> |
| | | <el-dialog |
| | | v-model="dialogVisible" |
| | | :title="dialogTitle" |
| | | width="800px" |
| | | :close-on-click-modal="false" |
| | | > |
| | | <el-form ref="formRef" :model="form" :rules="rules" label-width="120px"> |
| | | <el-dialog v-model="dialogVisible" |
| | | :title="dialogTitle" |
| | | width="800px" |
| | | :close-on-click-modal="false"> |
| | | <el-form ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="120px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="知识标题" prop="title"> |
| | | <el-input v-model="form.title" placeholder="请输入知识标题" /> |
| | | <el-form-item label="知识标题" |
| | | prop="title"> |
| | | <el-input v-model="form.title" |
| | | placeholder="请输入知识标题" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="知识类型" prop="type"> |
| | | <el-select v-model="form.type" placeholder="请选择知识类型" style="width: 100%"> |
| | | <el-option label="合同特批" value="contract" /> |
| | | <el-option label="审批案例" value="approval" /> |
| | | <el-option label="解决方案" value="solution" /> |
| | | <el-option label="经验总结" value="experience" /> |
| | | <el-option label="操作指南" value="guide" /> |
| | | <el-form-item label="知识类型" |
| | | prop="type"> |
| | | <el-select v-model="form.type" |
| | | placeholder="请选择知识类型" |
| | | style="width: 100%"> |
| | | <el-option label="合同特批" |
| | | value="contract" /> |
| | | <el-option label="审批案例" |
| | | value="approval" /> |
| | | <el-option label="解决方案" |
| | | value="solution" /> |
| | | <el-option label="经验总结" |
| | | value="experience" /> |
| | | <el-option label="操作指南" |
| | | value="guide" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="适用场景" prop="scenario"> |
| | | <el-input v-model="form.scenario" placeholder="请输入适用场景" /> |
| | | <el-form-item label="适用场景" |
| | | prop="scenario"> |
| | | <el-input v-model="form.scenario" |
| | | placeholder="请输入适用场景" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="解决效率" prop="efficiency"> |
| | | <el-select v-model="form.efficiency" placeholder="请选择解决效率" style="width: 100%"> |
| | | <el-option label="显著提升" value="high" /> |
| | | <el-option label="一般提升" value="medium" /> |
| | | <el-option label="轻微提升" value="low" /> |
| | | <el-form-item label="解决效率" |
| | | prop="efficiency"> |
| | | <el-select v-model="form.efficiency" |
| | | placeholder="请选择解决效率" |
| | | style="width: 100%"> |
| | | <el-option label="显著提升" |
| | | value="high" /> |
| | | <el-option label="一般提升" |
| | | value="medium" /> |
| | | <el-option label="轻微提升" |
| | | value="low" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="问题描述" prop="problem"> |
| | | <el-input |
| | | v-model="form.problem" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请描述遇到的问题" |
| | | /> |
| | | <el-form-item label="问题描述" |
| | | prop="problem"> |
| | | <el-input v-model="form.problem" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请描述遇到的问题" /> |
| | | </el-form-item> |
| | | <el-form-item label="解决方案" prop="solution"> |
| | | <el-input |
| | | v-model="form.solution" |
| | | type="textarea" |
| | | :rows="4" |
| | | placeholder="请详细描述解决方案" |
| | | /> |
| | | <el-form-item label="解决方案" |
| | | prop="solution"> |
| | | <el-input v-model="form.solution" |
| | | type="textarea" |
| | | :rows="4" |
| | | placeholder="请详细描述解决方案" /> |
| | | </el-form-item> |
| | | <el-form-item label="关键要点" prop="keyPoints"> |
| | | <el-input |
| | | v-model="form.keyPoints" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请输入关键要点,用逗号分隔" |
| | | /> |
| | | <el-form-item label="关键要点" |
| | | prop="keyPoints"> |
| | | <el-input v-model="form.keyPoints" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请输入关键要点,用逗号分隔" /> |
| | | </el-form-item> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="创建人" prop="creator"> |
| | | <el-input v-model="form.creator" placeholder="请输入创建人" /> |
| | | <el-form-item label="创建人" |
| | | prop="creator"> |
| | | <el-input v-model="form.creator" |
| | | placeholder="请输入创建人" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="使用次数" prop="usageCount"> |
| | | <el-input-number v-model="form.usageCount" :min="0" style="width: 100%" /> |
| | | <el-form-item label="使用次数" |
| | | prop="usageCount"> |
| | | <el-input-number v-model="form.usageCount" |
| | | :min="0" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="submitForm">确定</el-button> |
| | | <el-button @click="dialogVisible = false">取消</el-button> |
| | | <el-button type="primary" @click="submitForm">确定</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 查看知识详情弹窗 --> |
| | | <el-dialog |
| | | v-model="viewDialogVisible" |
| | | title="知识详情" |
| | | width="900px" |
| | | :close-on-click-modal="false" |
| | | > |
| | | <el-dialog v-model="viewDialogVisible" |
| | | title="知识详情" |
| | | width="900px" |
| | | :close-on-click-modal="false"> |
| | | <div class="knowledge-detail"> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions-item label="知识标题" :span="2"> |
| | | <el-descriptions :column="2" |
| | | border> |
| | | <el-descriptions-item label="知识标题" |
| | | :span="2"> |
| | | <span class="detail-title">{{ currentKnowledge.title }}</span> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="知识类型"> |
| | |
| | | {{ currentKnowledge.createTime }} |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div class="detail-section"> |
| | | <h4>问题描述</h4> |
| | | <div class="detail-content">{{ currentKnowledge.problem }}</div> |
| | | </div> |
| | | |
| | | <div class="detail-section"> |
| | | <h4>解决方案</h4> |
| | | <div class="detail-content">{{ currentKnowledge.solution }}</div> |
| | | </div> |
| | | |
| | | <div class="detail-section"> |
| | | <h4>关键要点</h4> |
| | | <div class="key-points"> |
| | | <el-tag |
| | | v-for="(point, index) in currentKnowledge.keyPoints.split(',')" |
| | | :key="index" |
| | | type="success" |
| | | style="margin-right: 8px; margin-bottom: 8px;" |
| | | > |
| | | <el-tag v-for="(point, index) in currentKnowledge.keyPoints.split(',')" |
| | | :key="index" |
| | | type="success" |
| | | style="margin-right: 8px; margin-bottom: 8px;"> |
| | | {{ point.trim() }} |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="detail-section"> |
| | | <h4>使用统计</h4> |
| | | <div class="usage-stats"> |
| | |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="copyKnowledge">复制知识</el-button> |
| | | <el-button @click="viewDialogVisible = false">关闭</el-button> |
| | | <el-button type="primary" @click="copyKnowledge">复制知识</el-button> |
| | | <!-- <el-button type="success" @click="markAsFavorite">收藏@</el-button> --> |
| | | </span> |
| | | </template> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { onMounted, ref, reactive, toRefs, getCurrentInstance } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | import { listKnowledgeBase, delKnowledgeBase,addKnowledgeBase,updateKnowledgeBase } from "@/api/collaborativeApproval/knowledgeBase.js"; |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { onMounted, ref, reactive, toRefs, getCurrentInstance } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | import { |
| | | listKnowledgeBase, |
| | | delKnowledgeBase, |
| | | addKnowledgeBase, |
| | | updateKnowledgeBase, |
| | | } from "@/api/collaborativeApproval/knowledgeBase.js"; |
| | | |
| | | // 表单验证规则 |
| | | const rules = { |
| | | title: [ |
| | | { required: true, message: "请输入知识标题", trigger: "blur" } |
| | | ], |
| | | type: [ |
| | | { required: true, message: "请选择知识类型", trigger: "change" } |
| | | ], |
| | | problem: [ |
| | | { required: true, message: "请描述遇到的问题", trigger: "blur" } |
| | | ], |
| | | solution: [ |
| | | { required: true, message: "请详细描述解决方案", trigger: "blur" } |
| | | ] |
| | | }; |
| | | |
| | | // 响应式数据 |
| | | const data = reactive({ |
| | | searchForm: { |
| | | title: "", |
| | | type: "", |
| | | }, |
| | | tableLoading: false, |
| | | page: { |
| | | current: 1, |
| | | size: 20, |
| | | total: 0, |
| | | }, |
| | | tableData: [], |
| | | selectedIds: [], |
| | | form: { |
| | | title: "", |
| | | type: "", |
| | | scenario: "", |
| | | efficiency: "", |
| | | problem: "", |
| | | solution: "", |
| | | keyPoints: "", |
| | | creator: "", |
| | | usageCount: 0 |
| | | }, |
| | | dialogVisible: false, |
| | | dialogTitle: "", |
| | | dialogType: "add", |
| | | viewDialogVisible: false, |
| | | currentKnowledge: {} |
| | | }); |
| | | |
| | | const { |
| | | searchForm, |
| | | tableLoading, |
| | | page, |
| | | tableData, |
| | | selectedIds, |
| | | form, |
| | | dialogVisible, |
| | | dialogTitle, |
| | | dialogType, |
| | | viewDialogVisible, |
| | | currentKnowledge |
| | | } = toRefs(data); |
| | | |
| | | // 表单引用 |
| | | const formRef = ref(); |
| | | |
| | | // 表格列配置 |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "知识标题", |
| | | prop: "title", |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "知识类型", |
| | | prop: "type", |
| | | dataType: "tag", |
| | | formatData: (params) => { |
| | | const typeMap = { |
| | | contract: "合同特批", |
| | | approval: "审批案例", |
| | | solution: "解决方案", |
| | | experience: "经验总结", |
| | | guide: "操作指南" |
| | | }; |
| | | return typeMap[params] || params; |
| | | }, |
| | | formatType: (params) => { |
| | | const typeMap = { |
| | | contract: "success", |
| | | approval: "warning", |
| | | solution: "primary", |
| | | experience: "info", |
| | | guide: "danger" |
| | | }; |
| | | return typeMap[params] || "info"; |
| | | } |
| | | }, |
| | | { |
| | | label: "适用场景", |
| | | prop: "scenario", |
| | | width: 150, |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "解决效率", |
| | | prop: "efficiency", |
| | | dataType: "tag", |
| | | formatData: (params) => { |
| | | const efficiencyMap = { |
| | | high: "显著提升", |
| | | medium: "一般提升", |
| | | low: "轻微提升" |
| | | }; |
| | | return efficiencyMap[params] || params; |
| | | }, |
| | | formatType: (params) => { |
| | | const typeMap = { |
| | | high: "success", |
| | | medium: "warning", |
| | | low: "info" |
| | | }; |
| | | return typeMap[params] || "info"; |
| | | } |
| | | }, |
| | | { |
| | | label: "使用次数", |
| | | prop: "usageCount", |
| | | width: 100, |
| | | align: "center" |
| | | }, |
| | | { |
| | | label: "创建人", |
| | | prop: "creator", |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: "创建时间", |
| | | prop: "createTime", |
| | | width: 180, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "操作", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 200, |
| | | operation: [ |
| | | { |
| | | name: "编辑", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | } |
| | | }, |
| | | { |
| | | name: "查看", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | viewKnowledge(row); |
| | | } |
| | | } |
| | | ] |
| | | } |
| | | ]); |
| | | |
| | | // 模拟数据 |
| | | // let mockData = [ |
| | | // { |
| | | // id: "1", |
| | | // title: "特殊合同审批流程优化方案", |
| | | // type: "contract", |
| | | // scenario: "大额合同快速审批", |
| | | // efficiency: "high", |
| | | // problem: "大额合同审批流程复杂,审批时间长,影响业务进展", |
| | | // solution: "建立绿色通道,对符合条件的合同采用简化审批流程,由部门负责人直接审批,平均审批时间从3天缩短至1天", |
| | | // keyPoints: "绿色通道条件,简化流程,审批权限,时间控制", |
| | | // creator: "张经理", |
| | | // usageCount: 15, |
| | | // createTime: "2024-01-15 10:30:00" |
| | | // }, |
| | | // { |
| | | // id: "2", |
| | | // title: "跨部门协作审批经验总结", |
| | | // type: "experience", |
| | | // scenario: "多部门协作项目", |
| | | // efficiency: "medium", |
| | | // problem: "跨部门项目审批时,各部门意见不统一,审批进度缓慢", |
| | | // solution: "建立项目协调机制,指定项目负责人,定期召开协调会议,统一各方意见后再进行审批", |
| | | // keyPoints: "项目协调,定期会议,统一意见,负责人制度", |
| | | // creator: "李主管", |
| | | // usageCount: 8, |
| | | // createTime: "2024-01-14 15:20:00" |
| | | // }, |
| | | // { |
| | | // id: "3", |
| | | // title: "紧急采购审批操作指南", |
| | | // type: "guide", |
| | | // scenario: "紧急采购需求", |
| | | // efficiency: "high", |
| | | // problem: "紧急采购时审批流程复杂,无法满足紧急需求", |
| | | // solution: "制定紧急采购审批标准,明确紧急程度分级,不同级别采用不同审批流程,确保紧急需求得到及时处理", |
| | | // keyPoints: "紧急分级,标准制定,流程简化,及时处理", |
| | | // creator: "王专员", |
| | | // usageCount: 12, |
| | | // createTime: "2024-01-13 09:15:00" |
| | | // } |
| | | // ]; |
| | | |
| | | // 知识标题模板 |
| | | const titleTemplates = [ |
| | | "{type}审批流程优化方案", |
| | | "{scenario}处理经验总结", |
| | | "{type}特殊情况处理指南", |
| | | "{scenario}快速审批方案", |
| | | "{type}标准化操作流程", |
| | | "{scenario}问题解决方案", |
| | | "{type}最佳实践总结", |
| | | "{scenario}效率提升方案" |
| | | ]; |
| | | |
| | | // 知识类型配置 |
| | | const knowledgeTypes = [ |
| | | { type: "contract", label: "合同特批", efficiency: "high" }, |
| | | { type: "approval", label: "审批案例", efficiency: "medium" }, |
| | | { type: "solution", label: "解决方案", efficiency: "high" }, |
| | | { type: "experience", label: "经验总结", efficiency: "medium" }, |
| | | { type: "guide", label: "操作指南", efficiency: "low" } |
| | | ]; |
| | | |
| | | // 场景列表 |
| | | const scenarios = ["大额合同审批", "跨部门协作", "紧急采购", "特殊申请", "流程优化", "问题处理", "标准化建设", "效率提升"]; |
| | | |
| | | // 自动生成新数据 |
| | | const generateNewData = () => { |
| | | const newId = (mockData.length + 1).toString(); |
| | | const now = new Date(); |
| | | const randomType = knowledgeTypes[Math.floor(Math.random() * knowledgeTypes.length)]; |
| | | const randomScenario = scenarios[Math.floor(Math.random() * scenarios.length)]; |
| | | |
| | | // 生成随机标题 |
| | | let title = titleTemplates[Math.floor(Math.random() * titleTemplates.length)]; |
| | | title = title |
| | | .replace('{type}', randomType.label) |
| | | .replace('{scenario}', randomScenario); |
| | | |
| | | const newKnowledge = { |
| | | id: newId, |
| | | title: title, |
| | | type: randomType.type, |
| | | scenario: randomScenario, |
| | | efficiency: randomType.efficiency, |
| | | problem: `在${randomScenario}过程中遇到的问题描述...`, |
| | | solution: `针对${randomScenario}的解决方案和操作步骤...`, |
| | | keyPoints: "关键要点1,关键要点2,关键要点3,关键要点4", |
| | | creator: ["张经理", "李主管", "王专员", "刘总监"][Math.floor(Math.random() * 4)], |
| | | usageCount: Math.floor(Math.random() * 20) + 1, |
| | | createTime: now.toLocaleString() |
| | | // 表单验证规则 |
| | | const rules = { |
| | | title: [{ required: true, message: "请输入知识标题", trigger: "blur" }], |
| | | type: [{ required: true, message: "请选择知识类型", trigger: "change" }], |
| | | problem: [{ required: true, message: "请描述遇到的问题", trigger: "blur" }], |
| | | solution: [ |
| | | { required: true, message: "请详细描述解决方案", trigger: "blur" }, |
| | | ], |
| | | }; |
| | | |
| | | // 添加到数据开头 |
| | | mockData.unshift(newKnowledge); |
| | | |
| | | // 保持数据量在合理范围内(最多保留30条) |
| | | if (mockData.length > 30) { |
| | | mockData = mockData.slice(0, 30); |
| | | } |
| | | |
| | | console.log(`[${new Date().toLocaleString()}] 自动生成新知识: ${title}`); |
| | | }; |
| | | |
| | | // 生命周期 |
| | | onMounted(() => { |
| | | getList(); |
| | | startAutoRefresh(); |
| | | }); |
| | | |
| | | // 开始自动刷新 |
| | | const startAutoRefresh = () => { |
| | | setInterval(() => { |
| | | generateNewData(); |
| | | getList(); |
| | | }, 600000); // 10分钟刷新一次 (10 * 60 * 1000 = 600000ms) |
| | | }; |
| | | |
| | | // 查询数据 |
| | | const handleQuery = () => { |
| | | page.value.current = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | listKnowledgeBase({...page.value, ...searchForm.value}) |
| | | .then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records |
| | | page.total = res.data.total; |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | }) |
| | | }; |
| | | |
| | | // 分页处理 |
| | | const pagination = (obj) => { |
| | | page.value.current = obj.page; |
| | | page.value.size = obj.limit; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | // 选择变化处理 |
| | | const handleSelectionChange = (selection) => { |
| | | selectedIds.value = selection.map(item => item.id); |
| | | }; |
| | | |
| | | // 打开表单 |
| | | const openForm = (type, row = null) => { |
| | | dialogType.value = type; |
| | | if (type === "add") { |
| | | dialogTitle.value = "新增知识"; |
| | | // 重置表单 |
| | | Object.assign(form.value, { |
| | | // 响应式数据 |
| | | const data = reactive({ |
| | | searchForm: { |
| | | title: "", |
| | | type: "", |
| | | }, |
| | | tableLoading: false, |
| | | page: { |
| | | current: 1, |
| | | size: 20, |
| | | total: 0, |
| | | }, |
| | | tableData: [], |
| | | selectedIds: [], |
| | | form: { |
| | | title: "", |
| | | type: "", |
| | | scenario: "", |
| | |
| | | solution: "", |
| | | keyPoints: "", |
| | | creator: "", |
| | | usageCount: 0 |
| | | }); |
| | | } else if (type === "edit" && row) { |
| | | dialogTitle.value = "编辑知识"; |
| | | Object.assign(form.value, { |
| | | id: row.id, |
| | | title: row.title, |
| | | type: row.type, |
| | | scenario: row.scenario, |
| | | efficiency: row.efficiency, |
| | | problem: row.problem, |
| | | solution: row.solution, |
| | | keyPoints: row.keyPoints, |
| | | creator: row.creator, |
| | | usageCount: row.usageCount |
| | | }); |
| | | } |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // 查看知识详情 |
| | | const viewKnowledge = (row) => { |
| | | currentKnowledge.value = { ...row }; |
| | | viewDialogVisible.value = true; |
| | | }; |
| | | |
| | | // 获取类型标签类型 |
| | | const getTypeTagType = (type) => { |
| | | const typeMap = { |
| | | contract: "success", |
| | | approval: "warning", |
| | | solution: "primary", |
| | | experience: "info", |
| | | guide: "danger" |
| | | }; |
| | | return typeMap[type] || "info"; |
| | | }; |
| | | |
| | | // 获取类型标签文本 |
| | | const getTypeLabel = (type) => { |
| | | const typeMap = { |
| | | contract: "合同特批", |
| | | approval: "审批案例", |
| | | solution: "解决方案", |
| | | experience: "经验总结", |
| | | guide: "操作指南" |
| | | }; |
| | | return typeMap[type] || type; |
| | | }; |
| | | |
| | | // 获取效率标签类型 |
| | | const getEfficiencyTagType = (efficiency) => { |
| | | const typeMap = { |
| | | high: "success", |
| | | medium: "warning", |
| | | low: "info" |
| | | }; |
| | | return typeMap[efficiency] || "info"; |
| | | }; |
| | | |
| | | // 获取效率标签文本 |
| | | const getEfficiencyLabel = (efficiency) => { |
| | | const efficiencyMap = { |
| | | high: "显著提升", |
| | | medium: "一般提升", |
| | | low: "轻微提升" |
| | | }; |
| | | return efficiencyMap[efficiency] || efficiency; |
| | | }; |
| | | |
| | | // 获取效率提升百分比 |
| | | const getEfficiencyScore = (efficiency) => { |
| | | const scoreMap = { |
| | | high: 40, |
| | | medium: 25, |
| | | low: 15 |
| | | }; |
| | | return scoreMap[efficiency] || 0; |
| | | }; |
| | | |
| | | // 获取平均节省时间 |
| | | const getTimeSaved = (efficiency) => { |
| | | const timeMap = { |
| | | high: "2-3天", |
| | | medium: "1-2天", |
| | | low: "0.5-1天" |
| | | }; |
| | | return timeMap[efficiency] || "未知"; |
| | | }; |
| | | |
| | | // 复制知识 |
| | | const copyKnowledge = () => { |
| | | const knowledgeText = ` |
| | | 知识标题:${currentKnowledge.value.title} |
| | | 知识类型:${getTypeLabel(currentKnowledge.value.type)} |
| | | 适用场景:${currentKnowledge.value.scenario} |
| | | 问题描述:${currentKnowledge.value.problem} |
| | | 解决方案:${currentKnowledge.value.solution} |
| | | 关键要点:${currentKnowledge.value.keyPoints} |
| | | 创建人:${currentKnowledge.value.creator} |
| | | `.trim(); |
| | | |
| | | // 复制到剪贴板 |
| | | navigator.clipboard.writeText(knowledgeText).then(() => { |
| | | ElMessage.success("知识内容已复制到剪贴板"); |
| | | }).catch(() => { |
| | | ElMessage.error("复制失败,请手动复制"); |
| | | usageCount: 0, |
| | | }, |
| | | dialogVisible: false, |
| | | dialogTitle: "", |
| | | dialogType: "add", |
| | | viewDialogVisible: false, |
| | | currentKnowledge: {}, |
| | | }); |
| | | }; |
| | | |
| | | // 收藏知识 |
| | | const markAsFavorite = () => { |
| | | // 增加使用次数 |
| | | const index = mockData.findIndex(item => item.id === currentKnowledge.value.id); |
| | | if (index !== -1) { |
| | | mockData[index].usageCount += 1; |
| | | currentKnowledge.value.usageCount += 1; |
| | | } |
| | | const { |
| | | searchForm, |
| | | tableLoading, |
| | | page, |
| | | tableData, |
| | | selectedIds, |
| | | form, |
| | | dialogVisible, |
| | | dialogTitle, |
| | | dialogType, |
| | | viewDialogVisible, |
| | | currentKnowledge, |
| | | } = toRefs(data); |
| | | |
| | | ElMessage.success("已收藏,使用次数+1"); |
| | | }; |
| | | // 表单引用 |
| | | const formRef = ref(); |
| | | |
| | | // 提交知识表单 |
| | | const submitForm = async () => { |
| | | try { |
| | | await formRef.value.validate(); |
| | | if (dialogType.value === "add") { |
| | | // 新增知识 |
| | | addKnowledgeBase({...form.value}).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("添加成功"); |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | } else { |
| | | updateKnowledgeBase({...form.value}).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("更新成功"); |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | // 表格列配置 |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "知识标题", |
| | | prop: "title", |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "知识类型", |
| | | prop: "type", |
| | | dataType: "tag", |
| | | formatData: params => { |
| | | const typeMap = { |
| | | contract: "合同特批", |
| | | approval: "审批案例", |
| | | solution: "解决方案", |
| | | experience: "经验总结", |
| | | guide: "操作指南", |
| | | }; |
| | | return typeMap[params] || params; |
| | | }, |
| | | formatType: params => { |
| | | const typeMap = { |
| | | contract: "success", |
| | | approval: "warning", |
| | | solution: "primary", |
| | | experience: "info", |
| | | guide: "danger", |
| | | }; |
| | | return typeMap[params] || "info"; |
| | | }, |
| | | }, |
| | | { |
| | | label: "适用场景", |
| | | prop: "scenario", |
| | | width: 150, |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "解决效率", |
| | | prop: "efficiency", |
| | | dataType: "tag", |
| | | formatData: params => { |
| | | const efficiencyMap = { |
| | | high: "显著提升", |
| | | medium: "一般提升", |
| | | low: "轻微提升", |
| | | }; |
| | | return efficiencyMap[params] || params; |
| | | }, |
| | | formatType: params => { |
| | | const typeMap = { |
| | | high: "success", |
| | | medium: "warning", |
| | | low: "info", |
| | | }; |
| | | return typeMap[params] || "info"; |
| | | }, |
| | | }, |
| | | { |
| | | label: "使用次数", |
| | | prop: "usageCount", |
| | | width: 100, |
| | | align: "center", |
| | | }, |
| | | { |
| | | label: "创建人", |
| | | prop: "creator", |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: "创建时间", |
| | | prop: "createTime", |
| | | width: 180, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "操作", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 200, |
| | | operation: [ |
| | | { |
| | | name: "编辑", |
| | | type: "text", |
| | | clickFun: row => { |
| | | openForm("edit", row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "查看", |
| | | type: "text", |
| | | clickFun: row => { |
| | | viewKnowledge(row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | |
| | | // 模拟数据 |
| | | // let mockData = [ |
| | | // { |
| | | // id: "1", |
| | | // title: "特殊合同审批流程优化方案", |
| | | // type: "contract", |
| | | // scenario: "大额合同快速审批", |
| | | // efficiency: "high", |
| | | // problem: "大额合同审批流程复杂,审批时间长,影响业务进展", |
| | | // solution: "建立绿色通道,对符合条件的合同采用简化审批流程,由部门负责人直接审批,平均审批时间从3天缩短至1天", |
| | | // keyPoints: "绿色通道条件,简化流程,审批权限,时间控制", |
| | | // creator: "张经理", |
| | | // usageCount: 15, |
| | | // createTime: "2024-01-15 10:30:00" |
| | | // }, |
| | | // { |
| | | // id: "2", |
| | | // title: "跨部门协作审批经验总结", |
| | | // type: "experience", |
| | | // scenario: "多部门协作项目", |
| | | // efficiency: "medium", |
| | | // problem: "跨部门项目审批时,各部门意见不统一,审批进度缓慢", |
| | | // solution: "建立项目协调机制,指定项目负责人,定期召开协调会议,统一各方意见后再进行审批", |
| | | // keyPoints: "项目协调,定期会议,统一意见,负责人制度", |
| | | // creator: "李主管", |
| | | // usageCount: 8, |
| | | // createTime: "2024-01-14 15:20:00" |
| | | // }, |
| | | // { |
| | | // id: "3", |
| | | // title: "紧急采购审批操作指南", |
| | | // type: "guide", |
| | | // scenario: "紧急采购需求", |
| | | // efficiency: "high", |
| | | // problem: "紧急采购时审批流程复杂,无法满足紧急需求", |
| | | // solution: "制定紧急采购审批标准,明确紧急程度分级,不同级别采用不同审批流程,确保紧急需求得到及时处理", |
| | | // keyPoints: "紧急分级,标准制定,流程简化,及时处理", |
| | | // creator: "王专员", |
| | | // usageCount: 12, |
| | | // createTime: "2024-01-13 09:15:00" |
| | | // } |
| | | // ]; |
| | | |
| | | // 知识标题模板 |
| | | const titleTemplates = [ |
| | | "{type}审批流程优化方案", |
| | | "{scenario}处理经验总结", |
| | | "{type}特殊情况处理指南", |
| | | "{scenario}快速审批方案", |
| | | "{type}标准化操作流程", |
| | | "{scenario}问题解决方案", |
| | | "{type}最佳实践总结", |
| | | "{scenario}效率提升方案", |
| | | ]; |
| | | |
| | | // 知识类型配置 |
| | | const knowledgeTypes = [ |
| | | { type: "contract", label: "合同特批", efficiency: "high" }, |
| | | { type: "approval", label: "审批案例", efficiency: "medium" }, |
| | | { type: "solution", label: "解决方案", efficiency: "high" }, |
| | | { type: "experience", label: "经验总结", efficiency: "medium" }, |
| | | { type: "guide", label: "操作指南", efficiency: "low" }, |
| | | ]; |
| | | |
| | | // 场景列表 |
| | | const scenarios = [ |
| | | "大额合同审批", |
| | | "跨部门协作", |
| | | "紧急采购", |
| | | "特殊申请", |
| | | "流程优化", |
| | | "问题处理", |
| | | "标准化建设", |
| | | "效率提升", |
| | | ]; |
| | | |
| | | // 自动生成新数据 |
| | | const generateNewData = () => { |
| | | const newId = (mockData.length + 1).toString(); |
| | | const now = new Date(); |
| | | const randomType = |
| | | knowledgeTypes[Math.floor(Math.random() * knowledgeTypes.length)]; |
| | | const randomScenario = |
| | | scenarios[Math.floor(Math.random() * scenarios.length)]; |
| | | |
| | | // 生成随机标题 |
| | | let title = titleTemplates[Math.floor(Math.random() * titleTemplates.length)]; |
| | | title = title |
| | | .replace("{type}", randomType.label) |
| | | .replace("{scenario}", randomScenario); |
| | | |
| | | const newKnowledge = { |
| | | id: newId, |
| | | title: title, |
| | | type: randomType.type, |
| | | scenario: randomScenario, |
| | | efficiency: randomType.efficiency, |
| | | problem: `在${randomScenario}过程中遇到的问题描述...`, |
| | | solution: `针对${randomScenario}的解决方案和操作步骤...`, |
| | | keyPoints: "关键要点1,关键要点2,关键要点3,关键要点4", |
| | | creator: ["张经理", "李主管", "王专员", "刘总监"][ |
| | | Math.floor(Math.random() * 4) |
| | | ], |
| | | usageCount: Math.floor(Math.random() * 20) + 1, |
| | | createTime: now.toLocaleString(), |
| | | }; |
| | | |
| | | // 添加到数据开头 |
| | | mockData.unshift(newKnowledge); |
| | | |
| | | // 保持数据量在合理范围内(最多保留30条) |
| | | if (mockData.length > 30) { |
| | | mockData = mockData.slice(0, 30); |
| | | } |
| | | } catch (error) { |
| | | console.error("表单验证失败:", error); |
| | | } |
| | | }; |
| | | |
| | | // 删除知识 |
| | | const handleDelete = () => { |
| | | if (selectedIds.value.length === 0) { |
| | | ElMessage.warning("请选择要删除的知识"); |
| | | return; |
| | | } |
| | | console.log(`[${new Date().toLocaleString()}] 自动生成新知识: ${title}`); |
| | | }; |
| | | |
| | | ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }).then(() => { |
| | | // console.log(selectedIds.value); |
| | | delKnowledgeBase(selectedIds.value).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("删除成功"); |
| | | selectedIds.value = []; |
| | | getList(); |
| | | } |
| | | }) |
| | | }).catch(() => { |
| | | // 用户取消 |
| | | // 生命周期 |
| | | onMounted(() => { |
| | | getList(); |
| | | startAutoRefresh(); |
| | | }); |
| | | }; |
| | | |
| | | // 导出 |
| | | const { proxy } = getCurrentInstance() |
| | | const handleExport = () => { |
| | | proxy.download('/knowledgeBase/export', { ...searchForm.value }, '知识库.xlsx') |
| | | } |
| | | // 开始自动刷新 |
| | | const startAutoRefresh = () => { |
| | | setInterval(() => { |
| | | generateNewData(); |
| | | getList(); |
| | | }, 600000); // 10分钟刷新一次 (10 * 60 * 1000 = 600000ms) |
| | | }; |
| | | |
| | | // 查询数据 |
| | | const handleQuery = () => { |
| | | page.value.current = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | listKnowledgeBase({ ...page.value, ...searchForm.value }) |
| | | .then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | page.total = res.data.total; |
| | | }) |
| | | .catch(err => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | // 分页处理 |
| | | const pagination = obj => { |
| | | page.value.current = obj.page; |
| | | page.value.size = obj.limit; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | // 选择变化处理 |
| | | const handleSelectionChange = selection => { |
| | | selectedIds.value = selection.map(item => item.id); |
| | | }; |
| | | |
| | | // 打开表单 |
| | | const openForm = (type, row = null) => { |
| | | dialogType.value = type; |
| | | if (type === "add") { |
| | | dialogTitle.value = "新增知识"; |
| | | // 重置表单 |
| | | Object.assign(form.value, { |
| | | title: "", |
| | | type: "", |
| | | scenario: "", |
| | | efficiency: "", |
| | | problem: "", |
| | | solution: "", |
| | | keyPoints: "", |
| | | creator: "", |
| | | usageCount: 0, |
| | | }); |
| | | } else if (type === "edit" && row) { |
| | | dialogTitle.value = "编辑知识"; |
| | | Object.assign(form.value, { |
| | | id: row.id, |
| | | title: row.title, |
| | | type: row.type, |
| | | scenario: row.scenario, |
| | | efficiency: row.efficiency, |
| | | problem: row.problem, |
| | | solution: row.solution, |
| | | keyPoints: row.keyPoints, |
| | | creator: row.creator, |
| | | usageCount: row.usageCount, |
| | | }); |
| | | } |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // 查看知识详情 |
| | | const viewKnowledge = row => { |
| | | currentKnowledge.value = { ...row }; |
| | | viewDialogVisible.value = true; |
| | | }; |
| | | |
| | | // 获取类型标签类型 |
| | | const getTypeTagType = type => { |
| | | const typeMap = { |
| | | contract: "success", |
| | | approval: "warning", |
| | | solution: "primary", |
| | | experience: "info", |
| | | guide: "danger", |
| | | }; |
| | | return typeMap[type] || "info"; |
| | | }; |
| | | |
| | | // 获取类型标签文本 |
| | | const getTypeLabel = type => { |
| | | const typeMap = { |
| | | contract: "合同特批", |
| | | approval: "审批案例", |
| | | solution: "解决方案", |
| | | experience: "经验总结", |
| | | guide: "操作指南", |
| | | }; |
| | | return typeMap[type] || type; |
| | | }; |
| | | |
| | | // 获取效率标签类型 |
| | | const getEfficiencyTagType = efficiency => { |
| | | const typeMap = { |
| | | high: "success", |
| | | medium: "warning", |
| | | low: "info", |
| | | }; |
| | | return typeMap[efficiency] || "info"; |
| | | }; |
| | | |
| | | // 获取效率标签文本 |
| | | const getEfficiencyLabel = efficiency => { |
| | | const efficiencyMap = { |
| | | high: "显著提升", |
| | | medium: "一般提升", |
| | | low: "轻微提升", |
| | | }; |
| | | return efficiencyMap[efficiency] || efficiency; |
| | | }; |
| | | |
| | | // 获取效率提升百分比 |
| | | const getEfficiencyScore = efficiency => { |
| | | const scoreMap = { |
| | | high: 40, |
| | | medium: 25, |
| | | low: 15, |
| | | }; |
| | | return scoreMap[efficiency] || 0; |
| | | }; |
| | | |
| | | // 获取平均节省时间 |
| | | const getTimeSaved = efficiency => { |
| | | const timeMap = { |
| | | high: "2-3天", |
| | | medium: "1-2天", |
| | | low: "0.5-1天", |
| | | }; |
| | | return timeMap[efficiency] || "未知"; |
| | | }; |
| | | |
| | | // 复制知识 |
| | | const copyKnowledge = () => { |
| | | const knowledgeText = ` |
| | | 知识标题:${currentKnowledge.value.title} |
| | | 知识类型:${getTypeLabel(currentKnowledge.value.type)} |
| | | 适用场景:${currentKnowledge.value.scenario} |
| | | 问题描述:${currentKnowledge.value.problem} |
| | | 解决方案:${currentKnowledge.value.solution} |
| | | 关键要点:${currentKnowledge.value.keyPoints} |
| | | 创建人:${currentKnowledge.value.creator} |
| | | `.trim(); |
| | | |
| | | // 复制到剪贴板 |
| | | navigator.clipboard |
| | | .writeText(knowledgeText) |
| | | .then(() => { |
| | | ElMessage.success("知识内容已复制到剪贴板"); |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error("复制失败,请手动复制"); |
| | | }); |
| | | }; |
| | | |
| | | // 收藏知识 |
| | | const markAsFavorite = () => { |
| | | // 增加使用次数 |
| | | const index = mockData.findIndex( |
| | | item => item.id === currentKnowledge.value.id |
| | | ); |
| | | if (index !== -1) { |
| | | mockData[index].usageCount += 1; |
| | | currentKnowledge.value.usageCount += 1; |
| | | } |
| | | |
| | | ElMessage.success("已收藏,使用次数+1"); |
| | | }; |
| | | |
| | | // 提交知识表单 |
| | | const submitForm = async () => { |
| | | try { |
| | | await formRef.value.validate(); |
| | | if (dialogType.value === "add") { |
| | | // 新增知识 |
| | | addKnowledgeBase({ ...form.value }) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("添加成功"); |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | } else { |
| | | updateKnowledgeBase({ ...form.value }) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("更新成功"); |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | } |
| | | } catch (error) { |
| | | console.error("表单验证失败:", error); |
| | | } |
| | | }; |
| | | |
| | | // 删除知识 |
| | | const handleDelete = () => { |
| | | if (selectedIds.value.length === 0) { |
| | | ElMessage.warning("请选择要删除的知识"); |
| | | return; |
| | | } |
| | | |
| | | ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | // console.log(selectedIds.value); |
| | | delKnowledgeBase(selectedIds.value).then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("删除成功"); |
| | | selectedIds.value = []; |
| | | getList(); |
| | | } |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | // 用户取消 |
| | | }); |
| | | }; |
| | | |
| | | // 导出 |
| | | const { proxy } = getCurrentInstance(); |
| | | const handleExport = () => { |
| | | proxy.download( |
| | | "/knowledgeBase/export", |
| | | { ...searchForm.value }, |
| | | "知识库.xlsx" |
| | | ); |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .auto-refresh-info { |
| | | margin-bottom: 15px; |
| | | } |
| | | .auto-refresh-info { |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .auto-refresh-info .el-alert { |
| | | border-radius: 8px; |
| | | } |
| | | .auto-refresh-info .el-alert { |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | |
| | | .knowledge-detail { |
| | | padding: 20px 0; |
| | | } |
| | | .knowledge-detail { |
| | | padding: 20px 0; |
| | | } |
| | | |
| | | .detail-title { |
| | | font-size: 18px; |
| | | font-weight: bold; |
| | | color: #303133; |
| | | } |
| | | .detail-title { |
| | | font-size: 18px; |
| | | font-weight: bold; |
| | | color: #303133; |
| | | } |
| | | |
| | | .detail-section { |
| | | margin-top: 24px; |
| | | } |
| | | .detail-section { |
| | | margin-top: 24px; |
| | | } |
| | | |
| | | .detail-section h4 { |
| | | margin: 0 0 12px 0; |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | border-left: 4px solid #409eff; |
| | | padding-left: 12px; |
| | | } |
| | | .detail-section h4 { |
| | | margin: 0 0 12px 0; |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | border-left: 4px solid #409eff; |
| | | padding-left: 12px; |
| | | } |
| | | |
| | | .detail-content { |
| | | background: #f8f9fa; |
| | | padding: 16px; |
| | | border-radius: 6px; |
| | | line-height: 1.6; |
| | | color: #606266; |
| | | white-space: pre-wrap; |
| | | } |
| | | .detail-content { |
| | | background: #f8f9fa; |
| | | padding: 16px; |
| | | border-radius: 6px; |
| | | line-height: 1.6; |
| | | color: #606266; |
| | | white-space: pre-wrap; |
| | | } |
| | | |
| | | .key-points { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 8px; |
| | | } |
| | | .key-points { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .usage-stats { |
| | | margin-top: 16px; |
| | | } |
| | | .usage-stats { |
| | | margin-top: 16px; |
| | | } |
| | | |
| | | .stat-item { |
| | | text-align: center; |
| | | padding: 20px; |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | } |
| | | .stat-item { |
| | | text-align: center; |
| | | padding: 20px; |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .stat-number { |
| | | font-size: 24px; |
| | | font-weight: bold; |
| | | color: #409eff; |
| | | margin-bottom: 8px; |
| | | } |
| | | .stat-number { |
| | | font-size: 24px; |
| | | font-weight: bold; |
| | | color: #409eff; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .stat-label { |
| | | font-size: 14px; |
| | | color: #909399; |
| | | } |
| | | .stat-label { |
| | | font-size: 14px; |
| | | color: #909399; |
| | | } |
| | | </style> |
| | |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">通知标题:</span> |
| | | <el-input |
| | | v-model="searchForm.title" |
| | | style="width: 240px" |
| | | placeholder="请输入通知标题搜索" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | /> |
| | | <el-input v-model="searchForm.title" |
| | | style="width: 240px" |
| | | placeholder="请输入通知标题搜索" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" /> |
| | | <span class="search_title ml10">通知类型:</span> |
| | | <el-select v-model="searchForm.type" clearable @change="handleQuery" style="width: 240px"> |
| | | <el-option label="放假通知" :value="'holiday'" /> |
| | | <el-option label="处罚通知" :value="'penalty'" /> |
| | | <el-option label="开会通知" :value="'meeting'" /> |
| | | <el-option label="临时通知" :value="'temporary'" /> |
| | | <el-option label="正式通知" :value="'formal'" /> |
| | | <el-select v-model="searchForm.type" |
| | | clearable |
| | | @change="handleQuery" |
| | | style="width: 240px"> |
| | | <el-option label="放假通知" |
| | | :value="'holiday'" /> |
| | | <el-option label="处罚通知" |
| | | :value="'penalty'" /> |
| | | <el-option label="开会通知" |
| | | :value="'meeting'" /> |
| | | <el-option label="临时通知" |
| | | :value="'temporary'" /> |
| | | <el-option label="正式通知" |
| | | :value="'formal'" /> |
| | | </el-select> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px"> |
| | | <el-button type="primary" |
| | | @click="handleQuery" |
| | | style="margin-left: 10px"> |
| | | 搜索 |
| | | </el-button> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" @click="openForm('add')">新增通知</el-button> |
| | | <el-button type="success" @click="openMeetingDialog">在线会议</el-button> |
| | | <el-button type="warning" @click="openFileShareDialog">文件共享</el-button> |
| | | <el-button type="primary" |
| | | @click="openForm('add')">新增通知</el-button> |
| | | <el-button type="success" |
| | | @click="openMeetingDialog">在线会议</el-button> |
| | | <el-button type="warning" |
| | | @click="openFileShareDialog">文件共享</el-button> |
| | | <!-- <el-button type="info" @click="refreshEmployees">刷新员工</el-button> --> |
| | | <el-button type="danger" plain @click="handleDelete">删除</el-button> |
| | | <el-button type="danger" |
| | | plain |
| | | @click="handleDelete">删除</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total" |
| | | ></PIMTable> |
| | | <PIMTable rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total"></PIMTable> |
| | | </div> |
| | | |
| | | <!-- 新增/编辑通知弹窗 --> |
| | | <el-dialog |
| | | v-model="dialogVisible" |
| | | :title="dialogTitle" |
| | | width="800px" |
| | | :close-on-click-modal="false" |
| | | > |
| | | <el-form ref="formRef" :model="form" :rules="rules" label-width="120px"> |
| | | <el-dialog v-model="dialogVisible" |
| | | :title="dialogTitle" |
| | | width="800px" |
| | | :close-on-click-modal="false"> |
| | | <el-form ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="120px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="通知标题" prop="title"> |
| | | <el-input v-model="form.title" placeholder="请输入通知标题" /> |
| | | <el-form-item label="通知标题" |
| | | prop="title"> |
| | | <el-input v-model="form.title" |
| | | placeholder="请输入通知标题" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="通知类型" prop="type"> |
| | | <el-select v-model="form.type" placeholder="请选择通知类型" style="width: 100%"> |
| | | <el-option label="放假通知" value="holiday" /> |
| | | <el-option label="处罚通知" value="penalty" /> |
| | | <el-option label="开会通知" value="meeting" /> |
| | | <el-option label="临时通知" value="temporary" /> |
| | | <el-option label="正式通知" value="formal" /> |
| | | <el-form-item label="通知类型" |
| | | prop="type"> |
| | | <el-select v-model="form.type" |
| | | placeholder="请选择通知类型" |
| | | style="width: 100%"> |
| | | <el-option label="放假通知" |
| | | value="holiday" /> |
| | | <el-option label="处罚通知" |
| | | value="penalty" /> |
| | | <el-option label="开会通知" |
| | | value="meeting" /> |
| | | <el-option label="临时通知" |
| | | value="temporary" /> |
| | | <el-option label="正式通知" |
| | | value="formal" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="优先级" prop="priority"> |
| | | <el-select v-model="form.priority" placeholder="请选择优先级" style="width: 100%"> |
| | | <el-option label="普通" value="low" /> |
| | | <el-option label="重要" value="medium" /> |
| | | <el-option label="紧急" value="high" /> |
| | | <el-form-item label="优先级" |
| | | prop="priority"> |
| | | <el-select v-model="form.priority" |
| | | placeholder="请选择优先级" |
| | | style="width: 100%"> |
| | | <el-option label="普通" |
| | | value="low" /> |
| | | <el-option label="重要" |
| | | value="medium" /> |
| | | <el-option label="紧急" |
| | | value="high" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="有效期至" prop="expireDate"> |
| | | <el-date-picker |
| | | v-model="form.expireDate" |
| | | type="date" |
| | | placeholder="请选择有效期" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | style="width: 100%" |
| | | /> |
| | | <el-form-item label="有效期至" |
| | | prop="expireDate"> |
| | | <el-date-picker v-model="form.expireDate" |
| | | type="date" |
| | | placeholder="请选择有效期" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="接收部门" prop="departments"> |
| | | <el-select |
| | | v-model="form.departments" |
| | | multiple |
| | | placeholder="请选择接收部门" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="dept in departments" |
| | | :key="dept" |
| | | :label="dept" |
| | | :value="dept" |
| | | /> |
| | | <el-form-item label="接收部门" |
| | | prop="departments"> |
| | | <el-select v-model="form.departments" |
| | | multiple |
| | | placeholder="请选择接收部门" |
| | | style="width: 100%"> |
| | | <el-option v-for="dept in departments" |
| | | :key="dept" |
| | | :label="dept" |
| | | :value="dept" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="同步方式" prop="syncMethods"> |
| | | <el-form-item label="同步方式" |
| | | prop="syncMethods"> |
| | | <el-checkbox-group v-model="form.syncMethods"> |
| | | <el-checkbox |
| | | v-for="method in syncMethods" |
| | | :key="method.value" |
| | | :label="method.value" |
| | | > |
| | | <el-checkbox v-for="method in syncMethods" |
| | | :key="method.value" |
| | | :label="method.value"> |
| | | {{ method.label }} |
| | | </el-checkbox> |
| | | </el-checkbox-group> |
| | | </el-form-item> |
| | | <el-form-item label="通知内容" prop="content"> |
| | | <el-input |
| | | v-model="form.content" |
| | | type="textarea" |
| | | :rows="4" |
| | | placeholder="请输入通知内容" |
| | | /> |
| | | <el-form-item label="通知内容" |
| | | prop="content"> |
| | | <el-input v-model="form.content" |
| | | type="textarea" |
| | | :rows="4" |
| | | placeholder="请输入通知内容" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="submitForm">确定</el-button> |
| | | <el-button @click="dialogVisible = false">取消</el-button> |
| | | <el-button type="primary" @click="submitForm">确定</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 在线会议弹窗 --> |
| | | <el-dialog |
| | | v-model="meetingDialogVisible" |
| | | title="创建在线会议" |
| | | width="700px" |
| | | :close-on-click-modal="false" |
| | | > |
| | | <el-form ref="meetingFormRef" :model="meetingForm" :rules="meetingRules" label-width="120px"> |
| | | <el-form-item label="会议标题" prop="title"> |
| | | <el-input v-model="meetingForm.title" placeholder="请输入会议标题" /> |
| | | <el-dialog v-model="meetingDialogVisible" |
| | | title="创建在线会议" |
| | | width="700px" |
| | | :close-on-click-modal="false"> |
| | | <el-form ref="meetingFormRef" |
| | | :model="meetingForm" |
| | | :rules="meetingRules" |
| | | label-width="120px"> |
| | | <el-form-item label="会议标题" |
| | | prop="title"> |
| | | <el-input v-model="meetingForm.title" |
| | | placeholder="请输入会议标题" /> |
| | | </el-form-item> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="开始时间" prop="startTime"> |
| | | <el-date-picker |
| | | v-model="meetingForm.startTime" |
| | | type="datetime" |
| | | value-format="YYYY-MM-DD HH:mm:ss" |
| | | format="YYYY-MM-DD HH:mm:ss" |
| | | placeholder="请选择开始时间" |
| | | style="width: 100%" |
| | | /> |
| | | <el-form-item label="开始时间" |
| | | prop="startTime"> |
| | | <el-date-picker v-model="meetingForm.startTime" |
| | | type="datetime" |
| | | value-format="YYYY-MM-DD HH:mm:ss" |
| | | format="YYYY-MM-DD HH:mm:ss" |
| | | placeholder="请选择开始时间" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="会议时长" prop="duration"> |
| | | <el-input-number |
| | | v-model="meetingForm.duration" |
| | | :min="15" |
| | | :max="480" |
| | | :step="15" |
| | | style="width: 100%" |
| | | /> |
| | | <el-form-item label="会议时长" |
| | | prop="duration"> |
| | | <el-input-number v-model="meetingForm.duration" |
| | | :min="15" |
| | | :max="480" |
| | | :step="15" |
| | | style="width: 100%" /> |
| | | <span style="margin-left: 10px">分钟</span> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="会议平台" prop="platform"> |
| | | <el-select v-model="meetingForm.platform" placeholder="请选择会议平台" style="width: 100%"> |
| | | <el-option |
| | | v-for="platform in meetingPlatforms" |
| | | :key="platform.value" |
| | | :label="platform.label" |
| | | :value="platform.value" |
| | | /> |
| | | <el-form-item label="会议平台" |
| | | prop="platform"> |
| | | <el-select v-model="meetingForm.platform" |
| | | placeholder="请选择会议平台" |
| | | style="width: 100%"> |
| | | <el-option v-for="platform in meetingPlatforms" |
| | | :key="platform.value" |
| | | :label="platform.label" |
| | | :value="platform.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="参会人员" prop="participants"> |
| | | <el-select |
| | | v-model="meetingForm.participants" |
| | | multiple |
| | | filterable |
| | | remote |
| | | :remote-method="filterEmployees" |
| | | :loading="employeesLoading" |
| | | placeholder="请选择参会人员" |
| | | style="width: 100%" |
| | | > |
| | | <el-option-group |
| | | v-for="group in employeeGroups" |
| | | :key="group.label" |
| | | :label="group.label" |
| | | > |
| | | <el-option |
| | | v-for="employee in group.options" |
| | | :key="employee.value" |
| | | :label="`${employee.label} (${employee.dept})`" |
| | | :value="employee.value" |
| | | > |
| | | <div style="display: flex; justify-content: space-between; align-items: center;"> |
| | | <div> |
| | | <div style="font-weight: 500;">{{ employee.label }}</div> |
| | | <div style="color: #909399; font-size: 12px;">{{ employee.dept }}</div> |
| | | </div> |
| | | <div style="text-align: right; font-size: 12px; color: #909399;"> |
| | | <div v-if="employee.phone">{{ employee.phone }}</div> |
| | | <div v-if="employee.email">{{ employee.email }}</div> |
| | | </div> |
| | | </div> |
| | | </el-option> |
| | | <el-form-item label="参会人员" |
| | | prop="participants"> |
| | | <el-select v-model="meetingForm.participants" |
| | | multiple |
| | | filterable |
| | | remote |
| | | :remote-method="filterEmployees" |
| | | :loading="employeesLoading" |
| | | placeholder="请选择参会人员" |
| | | style="width: 100%"> |
| | | <el-option-group v-for="group in employeeGroups" |
| | | :key="group.label" |
| | | :label="group.label"> |
| | | <el-option v-for="employee in group.options" |
| | | :key="employee.value" |
| | | :label="`${employee.label} (${employee.dept})`" |
| | | :value="employee.value"> |
| | | <div style="display: flex; justify-content: space-between; align-items: center;"> |
| | | <div> |
| | | <div style="font-weight: 500;">{{ employee.label }}</div> |
| | | <div style="color: #909399; font-size: 12px;">{{ employee.dept }}</div> |
| | | </div> |
| | | <div style="text-align: right; font-size: 12px; color: #909399;"> |
| | | <div v-if="employee.phone">{{ employee.phone }}</div> |
| | | <div v-if="employee.email">{{ employee.email }}</div> |
| | | </div> |
| | | </div> |
| | | </el-option> |
| | | </el-option-group> |
| | | </el-select> |
| | | <div style="margin-top: 8px; color: #909399; font-size: 12px;"> |
| | | 已选择 {{ meetingForm.participants.length }} 人 |
| | | </div> |
| | | <!-- 已选择人员详情 --> |
| | | <div v-if="meetingForm.participants.length > 0" style="margin-top: 10px;"> |
| | | <el-tag |
| | | v-for="participantId in meetingForm.participants" |
| | | :key="participantId" |
| | | closable |
| | | @close="removeParticipant(participantId)" |
| | | style="margin-right: 8px; margin-bottom: 8px;" |
| | | > |
| | | <div v-if="meetingForm.participants.length > 0" |
| | | style="margin-top: 10px;"> |
| | | <el-tag v-for="participantId in meetingForm.participants" |
| | | :key="participantId" |
| | | closable |
| | | @close="removeParticipant(participantId)" |
| | | style="margin-right: 8px; margin-bottom: 8px;"> |
| | | {{ getEmployeeName(participantId) }} |
| | | </el-tag> |
| | | </div> |
| | | </el-form-item> |
| | | <el-form-item label="会议描述" prop="description"> |
| | | <el-input |
| | | v-model="meetingForm.description" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请输入会议描述" |
| | | /> |
| | | <el-form-item label="会议描述" |
| | | prop="description"> |
| | | <el-input v-model="meetingForm.description" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请输入会议描述" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="createMeeting">创建会议</el-button> |
| | | <el-button @click="meetingDialogVisible = false">取消</el-button> |
| | | <el-button type="primary" @click="createMeeting">创建会议</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 文件共享弹窗 --> |
| | | <el-dialog |
| | | v-model="fileShareDialogVisible" |
| | | title="文件共享" |
| | | width="700px" |
| | | :close-on-click-modal="false" |
| | | > |
| | | <el-form ref="fileShareFormRef" :model="fileShareForm" :rules="fileShareRules" label-width="120px"> |
| | | <el-form-item label="共享标题" prop="title"> |
| | | <el-input v-model="fileShareForm.title" placeholder="请输入共享标题" /> |
| | | <el-dialog v-model="fileShareDialogVisible" |
| | | title="文件共享" |
| | | width="700px" |
| | | :close-on-click-modal="false"> |
| | | <el-form ref="fileShareFormRef" |
| | | :model="fileShareForm" |
| | | :rules="fileShareRules" |
| | | label-width="120px"> |
| | | <el-form-item label="共享标题" |
| | | prop="title"> |
| | | <el-input v-model="fileShareForm.title" |
| | | placeholder="请输入共享标题" /> |
| | | </el-form-item> |
| | | <el-form-item label="共享描述" prop="description"> |
| | | <el-input |
| | | v-model="fileShareForm.description" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请输入共享描述" |
| | | /> |
| | | <el-form-item label="共享描述" |
| | | prop="description"> |
| | | <el-input v-model="fileShareForm.description" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请输入共享描述" /> |
| | | </el-form-item> |
| | | <el-form-item label="接收部门" prop="departments"> |
| | | <el-select |
| | | v-model="fileShareForm.departments" |
| | | multiple |
| | | placeholder="请选择接收部门" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="dept in departments" |
| | | :key="dept" |
| | | :label="dept" |
| | | :value="dept" |
| | | /> |
| | | <el-form-item label="接收部门" |
| | | prop="departments"> |
| | | <el-select v-model="fileShareForm.departments" |
| | | multiple |
| | | placeholder="请选择接收部门" |
| | | style="width: 100%"> |
| | | <el-option v-for="dept in departments" |
| | | :key="dept" |
| | | :label="dept" |
| | | :value="dept" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="上传文件" prop="files"> |
| | | <el-upload |
| | | ref="uploadRef" |
| | | :auto-upload="false" |
| | | :on-change="handleFileChange" |
| | | :on-remove="removeFile" |
| | | :file-list="fileList" |
| | | multiple |
| | | :limit="10" |
| | | accept=".doc,.docx,.pdf,.xls,.xlsx,.ppt,.pptx,.txt,.jpg,.jpeg,.png,.gif" |
| | | > |
| | | <el-form-item label="上传文件" |
| | | prop="files"> |
| | | <el-upload ref="uploadRef" |
| | | :auto-upload="false" |
| | | :on-change="handleFileChange" |
| | | :on-remove="removeFile" |
| | | :file-list="fileList" |
| | | multiple |
| | | :limit="10" |
| | | accept=".doc,.docx,.pdf,.xls,.xlsx,.ppt,.pptx,.txt,.jpg,.jpeg,.png,.gif"> |
| | | <el-button type="primary">选择文件</el-button> |
| | | <template #tip> |
| | | <div class="el-upload__tip"> |
| | |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="shareFiles">共享文件</el-button> |
| | | <el-button @click="fileShareDialogVisible = false">取消</el-button> |
| | | <el-button type="primary" @click="shareFiles">共享文件</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { onMounted, ref, reactive, toRefs, computed } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | import { userListNoPageByTenantId } from "@/api/system/user.js"; |
| | | import { staffOnJobListPage } from "@/api/personnelManagement/employeeRecord.js"; |
| | | import { listNotification, addNotification, updateNotification, delNotification,addOnlineMeeting,addFileSharing } from "@/api/collaborativeApproval/notificationManagement.js"; |
| | | import { id } from "element-plus/es/locales.mjs"; |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { onMounted, ref, reactive, toRefs, computed } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | import { userListNoPageByTenantId } from "@/api/system/user.js"; |
| | | import { staffOnJobListPage } from "@/api/personnelManagement/employeeRecord.js"; |
| | | import { |
| | | listNotification, |
| | | addNotification, |
| | | updateNotification, |
| | | delNotification, |
| | | addOnlineMeeting, |
| | | addFileSharing, |
| | | } from "@/api/collaborativeApproval/notificationManagement.js"; |
| | | import { id } from "element-plus/es/locales.mjs"; |
| | | |
| | | // 表单验证规则 |
| | | const rules = { |
| | | title: [ |
| | | { required: true, message: "请输入通知标题", trigger: "blur" } |
| | | ], |
| | | type: [ |
| | | { required: true, message: "请选择通知类型", trigger: "change" } |
| | | ], |
| | | content: [ |
| | | { required: true, message: "请输入通知内容", trigger: "blur" } |
| | | ] |
| | | }; |
| | | |
| | | const meetingRules = { |
| | | title: [ |
| | | { required: true, message: "请输入会议标题", trigger: "blur" } |
| | | ], |
| | | startTime: [ |
| | | { required: true, message: "请选择会议开始时间", trigger: "change" } |
| | | ], |
| | | participants: [ |
| | | { required: true, message: "请选择参会人员", trigger: "change" } |
| | | ] |
| | | }; |
| | | |
| | | const fileShareRules = { |
| | | title: [ |
| | | { required: true, message: "请输入共享标题", trigger: "blur" } |
| | | ], |
| | | description: [ |
| | | { required: true, message: "请输入共享描述", trigger: "blur" } |
| | | ] |
| | | }; |
| | | |
| | | // 响应式数据 |
| | | const data = reactive({ |
| | | searchForm: { |
| | | title: "", |
| | | type: "", |
| | | status: "", |
| | | }, |
| | | tableLoading: false, |
| | | page: { |
| | | current: 1, |
| | | size: 20, |
| | | total: 0, |
| | | }, |
| | | tableData: [], |
| | | selectedIds: [], |
| | | // 新增通知相关 |
| | | form: { |
| | | title: "", |
| | | type: "", |
| | | priority: "", |
| | | content: "", |
| | | departments: [], |
| | | expireDate: "", |
| | | syncMethods: [] |
| | | }, |
| | | dialogVisible: false, |
| | | dialogTitle: "", |
| | | dialogType: "add", |
| | | // 在线会议相关 |
| | | meetingDialogVisible: false, |
| | | meetingForm: { |
| | | title: "", |
| | | startTime: "", |
| | | duration: 60, |
| | | participants: [], |
| | | description: "", |
| | | platform: "wechat" |
| | | }, |
| | | // 文件共享相关 |
| | | fileShareDialogVisible: false, |
| | | fileShareForm: { |
| | | title: "", |
| | | description: "", |
| | | departments: [], |
| | | files: [] |
| | | }, |
| | | fileList: [] |
| | | }); |
| | | |
| | | const { |
| | | searchForm, |
| | | tableLoading, |
| | | page, |
| | | tableData, |
| | | selectedIds, |
| | | form, |
| | | dialogVisible, |
| | | dialogTitle, |
| | | dialogType, |
| | | meetingDialogVisible, |
| | | meetingForm, |
| | | fileShareDialogVisible, |
| | | fileShareForm, |
| | | fileList |
| | | } = toRefs(data); |
| | | |
| | | // 表单引用 |
| | | const formRef = ref(); |
| | | const meetingFormRef = ref(); |
| | | const fileShareFormRef = ref(); |
| | | |
| | | // 表格列配置 |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "通知标题", |
| | | prop: "title", |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "通知类型", |
| | | prop: "type", |
| | | dataType: "tag", |
| | | formatData: (params) => { |
| | | const typeMap = { |
| | | holiday: "放假通知", |
| | | penalty: "处罚通知", |
| | | meeting: "开会通知", |
| | | temporary: "临时通知", |
| | | formal: "正式通知" |
| | | }; |
| | | return typeMap[params] || params; |
| | | }, |
| | | formatType: (params) => { |
| | | const typeMap = { |
| | | holiday: "success", |
| | | penalty: "danger", |
| | | meeting: "warning", |
| | | temporary: "info", |
| | | formal: "primary" |
| | | }; |
| | | return typeMap[params] || "info"; |
| | | } |
| | | }, |
| | | { |
| | | label: "优先级", |
| | | prop: "priority", |
| | | dataType: "tag", |
| | | formatData: (params) => { |
| | | const priorityMap = { |
| | | low: "普通", |
| | | medium: "重要", |
| | | high: "紧急" |
| | | }; |
| | | return priorityMap[params] || params; |
| | | }, |
| | | formatType: (params) => { |
| | | const typeMap = { |
| | | low: "info", |
| | | medium: "warning", |
| | | high: "danger" |
| | | }; |
| | | return typeMap[params] || "info"; |
| | | } |
| | | }, |
| | | { |
| | | label: "状态", |
| | | prop: "status", |
| | | dataType: "tag", |
| | | formatData: (params) => { |
| | | const statusMap = { |
| | | draft: "草稿", |
| | | published: "已发布", |
| | | expired: "已过期" |
| | | }; |
| | | return statusMap[params] || params; |
| | | }, |
| | | formatType: (params) => { |
| | | const typeMap = { |
| | | draft: "info", |
| | | published: "success", |
| | | expired: "danger" |
| | | }; |
| | | return typeMap[params] || "info"; |
| | | } |
| | | }, |
| | | { |
| | | label: "接收部门", |
| | | prop: "departments", |
| | | width: 150, |
| | | showOverflowTooltip: true, |
| | | formatData: (params) => { |
| | | if (!params || params.length === 0) return "全部部门"; |
| | | return params.join(", "); |
| | | } |
| | | }, |
| | | { |
| | | label: "有效期至", |
| | | prop: "expireDate", |
| | | width: 150, |
| | | formatData: (params) => { |
| | | if (!params) return "永久有效"; |
| | | return params; |
| | | } |
| | | }, |
| | | { |
| | | label: "创建时间", |
| | | prop: "createTime", |
| | | width: 180, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "操作", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 280, |
| | | operation: [ |
| | | { |
| | | name: "编辑", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | } |
| | | }, |
| | | { |
| | | name: "发布", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | publishNotification(row); |
| | | }, |
| | | // disabled: (row) => row.status === "published" |
| | | }, |
| | | { |
| | | name: "撤回", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | revokeNotification(row); |
| | | }, |
| | | // disabled: (row) => row.status !== "published" |
| | | } |
| | | ] |
| | | } |
| | | ]); |
| | | // 通知标题模板 |
| | | const titleTemplates = [ |
| | | "关于{year}年{holiday}放假安排的通知", |
| | | "{dept}部门{meeting}会议通知", |
| | | "员工{behavior}行为规范提醒", |
| | | "{company}重要事项通知", |
| | | "{dept}部门工作安排通知", |
| | | "关于{project}项目进度的通知", |
| | | "{dept}部门人员调整通知", |
| | | "公司{policy}政策更新通知" |
| | | ]; |
| | | |
| | | // 通知类型配置 |
| | | const notificationTypes = [ |
| | | { type: "holiday", label: "放假通知", priority: "high" }, |
| | | { type: "meeting", label: "开会通知", priority: "medium" }, |
| | | { type: "penalty", label: "处罚通知", priority: "high" }, |
| | | { type: "temporary", label: "临时通知", priority: "low" }, |
| | | { type: "formal", label: "正式通知", priority: "medium" } |
| | | ]; |
| | | |
| | | // 部门列表 |
| | | const departments = ["技术部", "销售部", "人事部", "财务部", "运营部", "市场部", "客服部"]; |
| | | |
| | | // 人员列表 |
| | | const employees = ref([]); |
| | | const employeesLoading = ref(false); |
| | | |
| | | // 获取在职员工列表 |
| | | const getEmployeesList = async () => { |
| | | try { |
| | | employeesLoading.value = true; |
| | | // 优先使用系统用户接口(按租户获取) |
| | | const userResponse = await userListNoPageByTenantId(); |
| | | |
| | | if (userResponse.data) { |
| | | employees.value = userResponse.data.map(user => ({ |
| | | label: user.nickName || user.userName || '未知姓名', |
| | | value: user.userId || user.id, |
| | | dept: user.dept?.deptName || '未知部门', |
| | | phone: user.phonenumber || '', |
| | | email: user.email || '', |
| | | status: user.status || '0' |
| | | })).filter(user => user.status === '0'); // 只显示正常状态的用户 |
| | | } else { |
| | | // 如果系统用户接口失败,使用员工台账接口 |
| | | const response = await staffOnJobListPage({ |
| | | pageNum: 1, |
| | | pageSize: 1000, |
| | | staffState: 1 // 在职状态 |
| | | }); |
| | | |
| | | if (response.data && response.data.records) { |
| | | employees.value = response.data.records.map(employee => ({ |
| | | label: employee.staffName || employee.name || '未知姓名', |
| | | value: employee.staffNo || employee.id || employee.staffId, |
| | | dept: employee.deptName || employee.department || '未知部门', |
| | | phone: employee.phone || employee.mobile || '', |
| | | email: employee.email || '', |
| | | status: '0' |
| | | })); |
| | | } |
| | | } |
| | | } catch (error) { |
| | | console.error('获取员工列表失败:', error); |
| | | // 如果接口都失败,使用默认数据 |
| | | employees.value = [ |
| | | { label: "张三", value: "001", dept: "技术部", phone: "13800138001", email: "zhangsan@company.com", status: "0" }, |
| | | { label: "李四", value: "002", dept: "销售部", phone: "13800138002", email: "lisi@company.com", status: "0" }, |
| | | { label: "王五", value: "003", dept: "人事部", phone: "13800138003", email: "wangwu@company.com", status: "0" } |
| | | ]; |
| | | } finally { |
| | | employeesLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | // 员工分组 |
| | | const employeeGroups = computed(() => { |
| | | const groups = {}; |
| | | employees.value.forEach(employee => { |
| | | const dept = employee.dept || '其他部门'; |
| | | if (!groups[dept]) { |
| | | groups[dept] = []; |
| | | } |
| | | groups[dept].push(employee); |
| | | }); |
| | | |
| | | // 按部门名称排序,确保显示顺序一致 |
| | | return Object.keys(groups) |
| | | .sort() |
| | | .map(dept => ({ |
| | | label: dept, |
| | | options: groups[dept].sort((a, b) => a.label.localeCompare(b.label, 'zh-CN')) |
| | | })); |
| | | }); |
| | | |
| | | // 过滤员工(远程搜索) |
| | | const filterEmployees = (query) => { |
| | | if (query !== '') { |
| | | const lowerQuery = query.toLowerCase(); |
| | | return employees.value.filter(employee => |
| | | employee.label.toLowerCase().includes(lowerQuery) || |
| | | employee.dept.toLowerCase().includes(lowerQuery) || |
| | | (employee.phone && employee.phone.includes(query)) || |
| | | (employee.email && employee.email.toLowerCase().includes(lowerQuery)) |
| | | ); |
| | | } else { |
| | | return employees.value; |
| | | } |
| | | }; |
| | | |
| | | // 刷新员工列表 |
| | | const refreshEmployees = async () => { |
| | | ElMessage.info("正在刷新员工列表..."); |
| | | await getEmployeesList(); |
| | | |
| | | // 统计各部门人数 |
| | | const deptStats = {}; |
| | | employees.value.forEach(emp => { |
| | | const dept = emp.dept || '其他部门'; |
| | | deptStats[dept] = (deptStats[dept] || 0) + 1; |
| | | }); |
| | | |
| | | const deptInfo = Object.entries(deptStats) |
| | | .map(([dept, count]) => `${dept}: ${count}人`) |
| | | .join(', '); |
| | | |
| | | ElMessage.success(`员工列表刷新完成,共 ${employees.value.length} 人 (${deptInfo})`); |
| | | }; |
| | | |
| | | // 获取员工姓名 |
| | | const getEmployeeName = (employeeId) => { |
| | | const employee = employees.value.find(emp => emp.value === employeeId); |
| | | return employee ? employee.label : '未知人员'; |
| | | }; |
| | | |
| | | // 获取员工详细信息 |
| | | const getEmployeeInfo = (employeeId) => { |
| | | const employee = employees.value.find(emp => emp.value === employeeId); |
| | | if (!employee) return null; |
| | | |
| | | return { |
| | | name: employee.label, |
| | | dept: employee.dept, |
| | | phone: employee.phone, |
| | | email: employee.email |
| | | }; |
| | | }; |
| | | |
| | | // 移除参会人员 |
| | | const removeParticipant = (participantId) => { |
| | | const index = meetingForm.value.participants.indexOf(participantId); |
| | | if (index > -1) { |
| | | meetingForm.value.participants.splice(index, 1); |
| | | } |
| | | }; |
| | | |
| | | // 同步方式选项 |
| | | const syncMethods = [ |
| | | { label: "企业微信", value: "wechat" }, |
| | | { label: "钉钉", value: "dingtalk" }, |
| | | { label: "邮件", value: "email" }, |
| | | { label: "短信", value: "sms" } |
| | | ]; |
| | | |
| | | // 会议平台选项 |
| | | const meetingPlatforms = [ |
| | | { label: "企业微信会议", value: "wechat" }, |
| | | { label: "钉钉会议", value: "dingtalk" }, |
| | | { label: "腾讯会议", value: "tencent" }, |
| | | { label: "Zoom", value: "zoom" } |
| | | ]; |
| | | |
| | | // 自动生成新数据 |
| | | const generateNewData = () => { |
| | | const newId = (mockData.length + 1).toString(); |
| | | const now = new Date(); |
| | | const randomType = notificationTypes[Math.floor(Math.random() * notificationTypes.length)]; |
| | | const randomDept = departments[Math.floor(Math.random() * departments.length)]; |
| | | |
| | | // 生成随机标题 |
| | | let title = titleTemplates[Math.floor(Math.random() * titleTemplates.length)]; |
| | | title = title |
| | | .replace('{year}', now.getFullYear()) |
| | | .replace('{holiday}', ['春节', '国庆', '中秋', '元旦'][Math.floor(Math.random() * 4)]) |
| | | .replace('{dept}', randomDept) |
| | | .replace('{meeting}', ['周例会', '月度总结', '项目评审', '培训会议'][Math.floor(Math.random() * 4)]) |
| | | .replace('{behavior}', ['考勤', '着装', '工作态度', '团队协作'][Math.floor(Math.random() * 4)]) |
| | | .replace('{company}', ['公司', '集团', '总部'][Math.floor(Math.random() * 4)]) |
| | | .replace('{project}', ['数字化转型', '产品升级', '市场拓展', '人才培养'][Math.floor(Math.random() * 4)]) |
| | | .replace('{policy}', ['考勤', '薪酬', '福利', '晋升'][Math.floor(Math.random() * 4)]); |
| | | |
| | | // 随机状态 |
| | | const statuses = ['draft', 'published']; |
| | | const randomStatus = statuses[Math.floor(Math.random() * statuses.length)]; |
| | | |
| | | // 随机优先级 |
| | | const priorities = ['low', 'medium', 'high']; |
| | | const randomPriority = priorities[Math.floor(Math.random() * priorities.length)]; |
| | | |
| | | const newNotification = { |
| | | id: newId, |
| | | title: title, |
| | | type: randomType.type, |
| | | priority: randomPriority, |
| | | status: randomStatus, |
| | | content: `这是${title}的详细内容,请相关人员注意查看...`, |
| | | departments: [randomDept], |
| | | expireDate: new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0], // 30天后过期 |
| | | syncMethods: ["wechat", "dingtalk"], |
| | | createTime: now.toLocaleString() |
| | | // 表单验证规则 |
| | | const rules = { |
| | | title: [{ required: true, message: "请输入通知标题", trigger: "blur" }], |
| | | type: [{ required: true, message: "请选择通知类型", trigger: "change" }], |
| | | content: [{ required: true, message: "请输入通知内容", trigger: "blur" }], |
| | | }; |
| | | |
| | | // 添加到数据开头 |
| | | mockData.unshift(newNotification); |
| | | const meetingRules = { |
| | | title: [{ required: true, message: "请输入会议标题", trigger: "blur" }], |
| | | startTime: [ |
| | | { required: true, message: "请选择会议开始时间", trigger: "change" }, |
| | | ], |
| | | participants: [ |
| | | { required: true, message: "请选择参会人员", trigger: "change" }, |
| | | ], |
| | | }; |
| | | |
| | | // 保持数据量在合理范围内(最多保留20条) |
| | | if (mockData.length > 20) { |
| | | mockData = mockData.slice(0, 20); |
| | | } |
| | | const fileShareRules = { |
| | | title: [{ required: true, message: "请输入共享标题", trigger: "blur" }], |
| | | description: [{ required: true, message: "请输入共享描述", trigger: "blur" }], |
| | | }; |
| | | |
| | | console.log(`[${new Date().toLocaleString()}] 自动生成新通知: ${title}`); |
| | | }; |
| | | |
| | | // 生命周期 |
| | | onMounted(() => { |
| | | getList(); |
| | | getEmployeesList(); // 获取员工列表 |
| | | startAutoRefresh(); |
| | | }); |
| | | |
| | | // 开始自动刷新 |
| | | const startAutoRefresh = () => { |
| | | setInterval(() => { |
| | | generateNewData(); |
| | | getList(); |
| | | }, 600000); // 10分钟刷新一次 (10 * 60 * 1000 = 600000ms) |
| | | }; |
| | | |
| | | // 查询数据 |
| | | const handleQuery = () => { |
| | | page.value.current = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | listNotification({...page.value, ...searchForm.value}) |
| | | .then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records |
| | | page.value.total = res.data.total; |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | }) |
| | | }; |
| | | |
| | | // 分页处理 |
| | | const pagination = (obj) => { |
| | | page.value.current = obj.page; |
| | | page.value.size = obj.limit; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | // 选择变化处理 |
| | | const handleSelectionChange = (selection) => { |
| | | selectedIds.value = selection.map(item => item.id); |
| | | }; |
| | | |
| | | // 打开表单 |
| | | const openForm = (type, row = null) => { |
| | | dialogType.value = type; |
| | | if (type === "add") { |
| | | dialogTitle.value = "新增通知"; |
| | | // 重置表单 |
| | | Object.assign(form.value, { |
| | | id: "", |
| | | // 响应式数据 |
| | | const data = reactive({ |
| | | searchForm: { |
| | | title: "", |
| | | type: "", |
| | | status: "", |
| | | }, |
| | | tableLoading: false, |
| | | page: { |
| | | current: 1, |
| | | size: 20, |
| | | total: 0, |
| | | }, |
| | | tableData: [], |
| | | selectedIds: [], |
| | | // 新增通知相关 |
| | | form: { |
| | | title: "", |
| | | type: "", |
| | | priority: "", |
| | | content: "", |
| | | departments: [], |
| | | expireDate: "", |
| | | status: "draft", |
| | | syncMethods: [] |
| | | syncMethods: [], |
| | | }, |
| | | dialogVisible: false, |
| | | dialogTitle: "", |
| | | dialogType: "add", |
| | | // 在线会议相关 |
| | | meetingDialogVisible: false, |
| | | meetingForm: { |
| | | title: "", |
| | | startTime: "", |
| | | duration: 60, |
| | | participants: [], |
| | | description: "", |
| | | platform: "wechat", |
| | | }, |
| | | // 文件共享相关 |
| | | fileShareDialogVisible: false, |
| | | fileShareForm: { |
| | | title: "", |
| | | description: "", |
| | | departments: [], |
| | | files: [], |
| | | }, |
| | | fileList: [], |
| | | }); |
| | | |
| | | const { |
| | | searchForm, |
| | | tableLoading, |
| | | page, |
| | | tableData, |
| | | selectedIds, |
| | | form, |
| | | dialogVisible, |
| | | dialogTitle, |
| | | dialogType, |
| | | meetingDialogVisible, |
| | | meetingForm, |
| | | fileShareDialogVisible, |
| | | fileShareForm, |
| | | fileList, |
| | | } = toRefs(data); |
| | | |
| | | // 表单引用 |
| | | const formRef = ref(); |
| | | const meetingFormRef = ref(); |
| | | const fileShareFormRef = ref(); |
| | | |
| | | // 表格列配置 |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "通知标题", |
| | | prop: "title", |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "通知类型", |
| | | prop: "type", |
| | | dataType: "tag", |
| | | formatData: params => { |
| | | const typeMap = { |
| | | holiday: "放假通知", |
| | | penalty: "处罚通知", |
| | | meeting: "开会通知", |
| | | temporary: "临时通知", |
| | | formal: "正式通知", |
| | | }; |
| | | return typeMap[params] || params; |
| | | }, |
| | | formatType: params => { |
| | | const typeMap = { |
| | | holiday: "success", |
| | | penalty: "danger", |
| | | meeting: "warning", |
| | | temporary: "info", |
| | | formal: "primary", |
| | | }; |
| | | return typeMap[params] || "info"; |
| | | }, |
| | | }, |
| | | { |
| | | label: "优先级", |
| | | prop: "priority", |
| | | dataType: "tag", |
| | | formatData: params => { |
| | | const priorityMap = { |
| | | low: "普通", |
| | | medium: "重要", |
| | | high: "紧急", |
| | | }; |
| | | return priorityMap[params] || params; |
| | | }, |
| | | formatType: params => { |
| | | const typeMap = { |
| | | low: "info", |
| | | medium: "warning", |
| | | high: "danger", |
| | | }; |
| | | return typeMap[params] || "info"; |
| | | }, |
| | | }, |
| | | { |
| | | label: "状态", |
| | | prop: "status", |
| | | dataType: "tag", |
| | | formatData: params => { |
| | | const statusMap = { |
| | | draft: "草稿", |
| | | published: "已发布", |
| | | expired: "已过期", |
| | | }; |
| | | return statusMap[params] || params; |
| | | }, |
| | | formatType: params => { |
| | | const typeMap = { |
| | | draft: "info", |
| | | published: "success", |
| | | expired: "danger", |
| | | }; |
| | | return typeMap[params] || "info"; |
| | | }, |
| | | }, |
| | | { |
| | | label: "接收部门", |
| | | prop: "departments", |
| | | width: 150, |
| | | showOverflowTooltip: true, |
| | | formatData: params => { |
| | | if (!params || params.length === 0) return "全部部门"; |
| | | return params.join(", "); |
| | | }, |
| | | }, |
| | | { |
| | | label: "有效期至", |
| | | prop: "expireDate", |
| | | width: 150, |
| | | formatData: params => { |
| | | if (!params) return "永久有效"; |
| | | return params; |
| | | }, |
| | | }, |
| | | { |
| | | label: "创建时间", |
| | | prop: "createTime", |
| | | width: 180, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "操作", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 280, |
| | | operation: [ |
| | | { |
| | | name: "编辑", |
| | | type: "text", |
| | | clickFun: row => { |
| | | openForm("edit", row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "发布", |
| | | type: "text", |
| | | clickFun: row => { |
| | | publishNotification(row); |
| | | }, |
| | | // disabled: (row) => row.status === "published" |
| | | }, |
| | | { |
| | | name: "撤回", |
| | | type: "text", |
| | | clickFun: row => { |
| | | revokeNotification(row); |
| | | }, |
| | | // disabled: (row) => row.status !== "published" |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | // 通知标题模板 |
| | | const titleTemplates = [ |
| | | "关于{year}年{holiday}放假安排的通知", |
| | | "{dept}部门{meeting}会议通知", |
| | | "员工{behavior}行为规范提醒", |
| | | "{company}重要事项通知", |
| | | "{dept}部门工作安排通知", |
| | | "关于{project}项目进度的通知", |
| | | "{dept}部门人员调整通知", |
| | | "公司{policy}政策更新通知", |
| | | ]; |
| | | |
| | | // 通知类型配置 |
| | | const notificationTypes = [ |
| | | { type: "holiday", label: "放假通知", priority: "high" }, |
| | | { type: "meeting", label: "开会通知", priority: "medium" }, |
| | | { type: "penalty", label: "处罚通知", priority: "high" }, |
| | | { type: "temporary", label: "临时通知", priority: "low" }, |
| | | { type: "formal", label: "正式通知", priority: "medium" }, |
| | | ]; |
| | | |
| | | // 部门列表 |
| | | const departments = [ |
| | | "技术部", |
| | | "销售部", |
| | | "人事部", |
| | | "财务部", |
| | | "运营部", |
| | | "市场部", |
| | | "客服部", |
| | | ]; |
| | | |
| | | // 人员列表 |
| | | const employees = ref([]); |
| | | const employeesLoading = ref(false); |
| | | |
| | | // 获取在职员工列表 |
| | | const getEmployeesList = async () => { |
| | | try { |
| | | employeesLoading.value = true; |
| | | // 优先使用系统用户接口(按租户获取) |
| | | const userResponse = await userListNoPageByTenantId(); |
| | | |
| | | if (userResponse.data) { |
| | | employees.value = userResponse.data |
| | | .map(user => ({ |
| | | label: user.nickName || user.userName || "未知姓名", |
| | | value: user.userId || user.id, |
| | | dept: user.dept?.deptName || "未知部门", |
| | | phone: user.phonenumber || "", |
| | | email: user.email || "", |
| | | status: user.status || "0", |
| | | })) |
| | | .filter(user => user.status === "0"); // 只显示正常状态的用户 |
| | | } else { |
| | | // 如果系统用户接口失败,使用员工台账接口 |
| | | const response = await staffOnJobListPage({ |
| | | pageNum: 1, |
| | | pageSize: 1000, |
| | | staffState: 1, // 在职状态 |
| | | }); |
| | | |
| | | if (response.data && response.data.records) { |
| | | employees.value = response.data.records.map(employee => ({ |
| | | label: employee.staffName || employee.name || "未知姓名", |
| | | value: employee.staffNo || employee.id || employee.staffId, |
| | | dept: employee.deptName || employee.department || "未知部门", |
| | | phone: employee.phone || employee.mobile || "", |
| | | email: employee.email || "", |
| | | status: "0", |
| | | })); |
| | | } |
| | | } |
| | | } catch (error) { |
| | | console.error("获取员工列表失败:", error); |
| | | // 如果接口都失败,使用默认数据 |
| | | employees.value = [ |
| | | { |
| | | label: "张三", |
| | | value: "001", |
| | | dept: "技术部", |
| | | phone: "13800138001", |
| | | email: "zhangsan@company.com", |
| | | status: "0", |
| | | }, |
| | | { |
| | | label: "李四", |
| | | value: "002", |
| | | dept: "销售部", |
| | | phone: "13800138002", |
| | | email: "lisi@company.com", |
| | | status: "0", |
| | | }, |
| | | { |
| | | label: "王五", |
| | | value: "003", |
| | | dept: "人事部", |
| | | phone: "13800138003", |
| | | email: "wangwu@company.com", |
| | | status: "0", |
| | | }, |
| | | ]; |
| | | } finally { |
| | | employeesLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | // 员工分组 |
| | | const employeeGroups = computed(() => { |
| | | const groups = {}; |
| | | employees.value.forEach(employee => { |
| | | const dept = employee.dept || "其他部门"; |
| | | if (!groups[dept]) { |
| | | groups[dept] = []; |
| | | } |
| | | groups[dept].push(employee); |
| | | }); |
| | | } else if (type === "edit" && row) { |
| | | dialogTitle.value = "编辑通知"; |
| | | |
| | | // 按部门名称排序,确保显示顺序一致 |
| | | return Object.keys(groups) |
| | | .sort() |
| | | .map(dept => ({ |
| | | label: dept, |
| | | options: groups[dept].sort((a, b) => |
| | | a.label.localeCompare(b.label, "zh-CN") |
| | | ), |
| | | })); |
| | | }); |
| | | |
| | | // 过滤员工(远程搜索) |
| | | const filterEmployees = query => { |
| | | if (query !== "") { |
| | | const lowerQuery = query.toLowerCase(); |
| | | return employees.value.filter( |
| | | employee => |
| | | employee.label.toLowerCase().includes(lowerQuery) || |
| | | employee.dept.toLowerCase().includes(lowerQuery) || |
| | | (employee.phone && employee.phone.includes(query)) || |
| | | (employee.email && employee.email.toLowerCase().includes(lowerQuery)) |
| | | ); |
| | | } else { |
| | | return employees.value; |
| | | } |
| | | }; |
| | | |
| | | // 刷新员工列表 |
| | | const refreshEmployees = async () => { |
| | | ElMessage.info("正在刷新员工列表..."); |
| | | await getEmployeesList(); |
| | | |
| | | // 统计各部门人数 |
| | | const deptStats = {}; |
| | | employees.value.forEach(emp => { |
| | | const dept = emp.dept || "其他部门"; |
| | | deptStats[dept] = (deptStats[dept] || 0) + 1; |
| | | }); |
| | | |
| | | const deptInfo = Object.entries(deptStats) |
| | | .map(([dept, count]) => `${dept}: ${count}人`) |
| | | .join(", "); |
| | | |
| | | ElMessage.success( |
| | | `员工列表刷新完成,共 ${employees.value.length} 人 (${deptInfo})` |
| | | ); |
| | | }; |
| | | |
| | | // 获取员工姓名 |
| | | const getEmployeeName = employeeId => { |
| | | const employee = employees.value.find(emp => emp.value === employeeId); |
| | | return employee ? employee.label : "未知人员"; |
| | | }; |
| | | |
| | | // 获取员工详细信息 |
| | | const getEmployeeInfo = employeeId => { |
| | | const employee = employees.value.find(emp => emp.value === employeeId); |
| | | if (!employee) return null; |
| | | |
| | | return { |
| | | name: employee.label, |
| | | dept: employee.dept, |
| | | phone: employee.phone, |
| | | email: employee.email, |
| | | }; |
| | | }; |
| | | |
| | | // 移除参会人员 |
| | | const removeParticipant = participantId => { |
| | | const index = meetingForm.value.participants.indexOf(participantId); |
| | | if (index > -1) { |
| | | meetingForm.value.participants.splice(index, 1); |
| | | } |
| | | }; |
| | | |
| | | // 同步方式选项 |
| | | const syncMethods = [ |
| | | { label: "企业微信", value: "wechat" }, |
| | | { label: "钉钉", value: "dingtalk" }, |
| | | { label: "邮件", value: "email" }, |
| | | { label: "短信", value: "sms" }, |
| | | ]; |
| | | |
| | | // 会议平台选项 |
| | | const meetingPlatforms = [ |
| | | { label: "企业微信会议", value: "wechat" }, |
| | | { label: "钉钉会议", value: "dingtalk" }, |
| | | { label: "腾讯会议", value: "tencent" }, |
| | | { label: "Zoom", value: "zoom" }, |
| | | ]; |
| | | |
| | | // 自动生成新数据 |
| | | const generateNewData = () => { |
| | | const newId = (mockData.length + 1).toString(); |
| | | const now = new Date(); |
| | | const randomType = |
| | | notificationTypes[Math.floor(Math.random() * notificationTypes.length)]; |
| | | const randomDept = |
| | | departments[Math.floor(Math.random() * departments.length)]; |
| | | |
| | | // 生成随机标题 |
| | | let title = titleTemplates[Math.floor(Math.random() * titleTemplates.length)]; |
| | | title = title |
| | | .replace("{year}", now.getFullYear()) |
| | | .replace( |
| | | "{holiday}", |
| | | ["春节", "国庆", "中秋", "元旦"][Math.floor(Math.random() * 4)] |
| | | ) |
| | | .replace("{dept}", randomDept) |
| | | .replace( |
| | | "{meeting}", |
| | | ["周例会", "月度总结", "项目评审", "培训会议"][ |
| | | Math.floor(Math.random() * 4) |
| | | ] |
| | | ) |
| | | .replace( |
| | | "{behavior}", |
| | | ["考勤", "着装", "工作态度", "团队协作"][Math.floor(Math.random() * 4)] |
| | | ) |
| | | .replace( |
| | | "{company}", |
| | | ["公司", "集团", "总部"][Math.floor(Math.random() * 4)] |
| | | ) |
| | | .replace( |
| | | "{project}", |
| | | ["数字化转型", "产品升级", "市场拓展", "人才培养"][ |
| | | Math.floor(Math.random() * 4) |
| | | ] |
| | | ) |
| | | .replace( |
| | | "{policy}", |
| | | ["考勤", "薪酬", "福利", "晋升"][Math.floor(Math.random() * 4)] |
| | | ); |
| | | |
| | | // 随机状态 |
| | | const statuses = ["draft", "published"]; |
| | | const randomStatus = statuses[Math.floor(Math.random() * statuses.length)]; |
| | | |
| | | // 随机优先级 |
| | | const priorities = ["low", "medium", "high"]; |
| | | const randomPriority = |
| | | priorities[Math.floor(Math.random() * priorities.length)]; |
| | | |
| | | const newNotification = { |
| | | id: newId, |
| | | title: title, |
| | | type: randomType.type, |
| | | priority: randomPriority, |
| | | status: randomStatus, |
| | | content: `这是${title}的详细内容,请相关人员注意查看...`, |
| | | departments: [randomDept], |
| | | expireDate: new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000) |
| | | .toISOString() |
| | | .split("T")[0], // 30天后过期 |
| | | syncMethods: ["wechat", "dingtalk"], |
| | | createTime: now.toLocaleString(), |
| | | }; |
| | | |
| | | // 添加到数据开头 |
| | | mockData.unshift(newNotification); |
| | | |
| | | // 保持数据量在合理范围内(最多保留20条) |
| | | if (mockData.length > 20) { |
| | | mockData = mockData.slice(0, 20); |
| | | } |
| | | |
| | | console.log(`[${new Date().toLocaleString()}] 自动生成新通知: ${title}`); |
| | | }; |
| | | |
| | | // 生命周期 |
| | | onMounted(() => { |
| | | getList(); |
| | | getEmployeesList(); // 获取员工列表 |
| | | startAutoRefresh(); |
| | | }); |
| | | |
| | | // 开始自动刷新 |
| | | const startAutoRefresh = () => { |
| | | setInterval(() => { |
| | | generateNewData(); |
| | | getList(); |
| | | }, 600000); // 10分钟刷新一次 (10 * 60 * 1000 = 600000ms) |
| | | }; |
| | | |
| | | // 查询数据 |
| | | const handleQuery = () => { |
| | | page.value.current = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | listNotification({ ...page.value, ...searchForm.value }) |
| | | .then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | page.value.total = res.data.total; |
| | | }) |
| | | .catch(err => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | // 分页处理 |
| | | const pagination = obj => { |
| | | page.value.current = obj.page; |
| | | page.value.size = obj.limit; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | // 选择变化处理 |
| | | const handleSelectionChange = selection => { |
| | | selectedIds.value = selection.map(item => item.id); |
| | | }; |
| | | |
| | | // 打开表单 |
| | | const openForm = (type, row = null) => { |
| | | dialogType.value = type; |
| | | if (type === "add") { |
| | | dialogTitle.value = "新增通知"; |
| | | // 重置表单 |
| | | Object.assign(form.value, { |
| | | id: "", |
| | | title: "", |
| | | type: "", |
| | | priority: "", |
| | | content: "", |
| | | departments: [], |
| | | expireDate: "", |
| | | status: "draft", |
| | | syncMethods: [], |
| | | }); |
| | | } else if (type === "edit" && row) { |
| | | dialogTitle.value = "编辑通知"; |
| | | Object.assign(form.value, { |
| | | id: row.id, |
| | | title: row.title, |
| | | type: row.type, |
| | | priority: row.priority, |
| | | content: row.content || "", |
| | | departments: row.departments || [], |
| | | expireDate: row.expireDate || "", |
| | | status: row.status, |
| | | syncMethods: row.syncMethods || [], |
| | | }); |
| | | } |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // 打开在线会议弹窗 |
| | | const openMeetingDialog = () => { |
| | | // 重置表单 |
| | | Object.assign(meetingForm.value, { |
| | | title: "", |
| | | startTime: "", |
| | | duration: 60, |
| | | participants: [], |
| | | description: "", |
| | | platform: "wechat", |
| | | }); |
| | | meetingDialogVisible.value = true; |
| | | }; |
| | | |
| | | // 打开文件共享弹窗 |
| | | const openFileShareDialog = () => { |
| | | // 重置表单 |
| | | Object.assign(fileShareForm.value, { |
| | | title: "", |
| | | description: "", |
| | | departments: [], |
| | | files: [], |
| | | }); |
| | | fileList.value = []; |
| | | fileShareDialogVisible.value = true; |
| | | }; |
| | | |
| | | // 手动刷新数据 |
| | | const manualRefresh = () => { |
| | | generateNewData(); |
| | | getList(); |
| | | ElMessage.success("手动刷新完成,已生成新通知"); |
| | | }; |
| | | |
| | | // 提交通知表单 |
| | | const submitForm = async () => { |
| | | try { |
| | | await formRef.value.validate(); |
| | | |
| | | if (dialogType.value === "add") { |
| | | // 新增通知 |
| | | addNotification({ ...form.value }) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("添加成功"); |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | } else { |
| | | // 编辑通知 |
| | | updateNotification({ ...form.value }) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("更新成功"); |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | } |
| | | } catch (error) { |
| | | console.error("表单验证失败:", error); |
| | | } |
| | | }; |
| | | |
| | | // 创建会议 |
| | | const createMeeting = async () => { |
| | | try { |
| | | await meetingFormRef.value.validate(); |
| | | |
| | | // 模拟创建会议 |
| | | const meetingInfo = { |
| | | title: meetingForm.value.title, |
| | | startTime: meetingForm.value.startTime, |
| | | duration: meetingForm.value.duration, |
| | | participants: meetingForm.value.participants, |
| | | description: meetingForm.value.description, |
| | | platform: meetingForm.value.platform, |
| | | }; |
| | | // 新增会议 |
| | | addOnlineMeeting({ ...meetingInfo }) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("会议添加成功"); |
| | | meetingDialogVisible.value = false; |
| | | getList(); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | // 模拟发送到企业微信/钉钉 |
| | | // const platformName = meetingPlatforms.find(p => p.value === meetingForm.value.platform)?.label || "未知平台"; |
| | | // ElMessage.success(`会议创建成功!会议ID: ${meetingInfo.meetingId},将通过${platformName}发送通知`); |
| | | |
| | | // 获取参会人员信息 |
| | | const participantNames = meetingForm.value.participants |
| | | .map(participantId => { |
| | | const employee = employees.value.find( |
| | | emp => emp.value === participantId |
| | | ); |
| | | return employee ? employee.label : "未知人员"; |
| | | }) |
| | | .join("、"); |
| | | |
| | | // 获取参会人员详细信息 |
| | | const participantDetails = meetingForm.value.participants |
| | | .map(participantId => { |
| | | const employee = employees.value.find( |
| | | emp => emp.value === participantId |
| | | ); |
| | | return employee |
| | | ? { |
| | | name: employee.label, |
| | | dept: employee.dept, |
| | | phone: employee.phone, |
| | | email: employee.email, |
| | | } |
| | | : null; |
| | | }) |
| | | .filter(Boolean); |
| | | |
| | | // 将会议信息添加到通知列表 |
| | | const meetingNotification = { |
| | | title: `[会议通知] ${meetingInfo.title}`, |
| | | type: "meeting", |
| | | priority: "high", |
| | | status: "published", |
| | | content: `会议时间: ${meetingInfo.startTime},时长: ${ |
| | | meetingInfo.duration |
| | | }分钟,平台: ${ |
| | | meetingPlatforms.find(p => p.value === meetingForm.value.platform) |
| | | ?.label || "未知平台" |
| | | },参会人员: ${participantNames},共${participantDetails.length}人`, |
| | | departments: [], |
| | | expireDate: "", |
| | | syncMethods: [meetingForm.value.platform], |
| | | }; |
| | | addNotification({ ...meetingNotification }) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("添加成功"); |
| | | // dialogVisible.value = false; |
| | | getList(); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | // mockData.unshift(meetingNotification); |
| | | // getList(); |
| | | } catch (error) { |
| | | console.error("会议表单验证失败:", error); |
| | | } |
| | | }; |
| | | |
| | | // 文件上传处理 |
| | | const handleFileChange = file => { |
| | | const isLt10M = file.size / 1024 / 1024 < 10; |
| | | if (!isLt10M) { |
| | | ElMessage.error("上传文件大小不能超过 10MB!"); |
| | | return false; |
| | | } |
| | | |
| | | const fileInfo = { |
| | | name: file.name, |
| | | size: file.size, |
| | | type: file.type, |
| | | uid: file.uid, |
| | | }; |
| | | |
| | | fileList.value.push(fileInfo); |
| | | fileShareForm.value.files.push(fileInfo.name); |
| | | return false; // 阻止自动上传 |
| | | }; |
| | | |
| | | // 移除文件 |
| | | const removeFile = file => { |
| | | const index = fileList.value.findIndex(item => item.uid === file.uid); |
| | | if (index !== -1) { |
| | | const index2 = fileShareForm.value.files.findIndex( |
| | | item => item.uid === file.uid |
| | | ); |
| | | if (index2 !== -1) { |
| | | fileShareForm.value.files.splice(index2, 1); |
| | | } |
| | | fileList.value.splice(index, 1); |
| | | } |
| | | }; |
| | | |
| | | // 共享文件 |
| | | const shareFiles = async () => { |
| | | try { |
| | | await fileShareFormRef.value.validate(); |
| | | |
| | | if (fileShareForm.value.files.length === 0) { |
| | | ElMessage.warning("请至少选择一个文件"); |
| | | return; |
| | | } |
| | | |
| | | // 模拟文件共享 |
| | | const shareInfo = { |
| | | title: fileShareForm.value.title, |
| | | description: fileShareForm.value.description, |
| | | departments: fileShareForm.value.departments, |
| | | files: fileShareForm.value.files, |
| | | }; |
| | | addFileSharing({ ...shareInfo }) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("文件共享成功"); |
| | | fileShareDialogVisible.value = false; |
| | | getList(); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | |
| | | // ElMessage.success(`文件共享成功!共享ID: ${shareInfo.shareId},已通知相关部门`); |
| | | |
| | | // 将文件共享信息添加到通知列表 |
| | | const fileShareNotification = { |
| | | title: `[文件共享] ${shareInfo.title}`, |
| | | type: "temporary", |
| | | priority: "medium", |
| | | status: "published", |
| | | content: `共享描述: ${shareInfo.description},文件数量: ${shareInfo.files.length}个`, |
| | | departments: shareInfo.departments, |
| | | expireDate: "", |
| | | syncMethods: ["wechat", "dingtalk"], |
| | | }; |
| | | addNotification({ ...fileShareNotification }) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("添加成功"); |
| | | // dialogVisible.value = false; |
| | | getList(); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | |
| | | // mockData.unshift(fileShareNotification); |
| | | // getList(); |
| | | } catch (error) { |
| | | console.error("文件共享表单验证失败:", error); |
| | | } |
| | | }; |
| | | |
| | | // 发布通知 |
| | | const publishNotification = row => { |
| | | Object.assign(form.value, { |
| | | id: row.id, |
| | | title: row.title, |
| | |
| | | departments: row.departments || [], |
| | | expireDate: row.expireDate || "", |
| | | status: row.status, |
| | | syncMethods: row.syncMethods || [] |
| | | syncMethods: row.syncMethods || [], |
| | | }); |
| | | } |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // 打开在线会议弹窗 |
| | | const openMeetingDialog = () => { |
| | | // 重置表单 |
| | | Object.assign(meetingForm.value, { |
| | | title: "", |
| | | startTime: "", |
| | | duration: 60, |
| | | participants: [], |
| | | description: "", |
| | | platform: "wechat" |
| | | }); |
| | | meetingDialogVisible.value = true; |
| | | }; |
| | | |
| | | // 打开文件共享弹窗 |
| | | const openFileShareDialog = () => { |
| | | // 重置表单 |
| | | Object.assign(fileShareForm.value, { |
| | | title: "", |
| | | description: "", |
| | | departments: [], |
| | | files: [] |
| | | }); |
| | | fileList.value = []; |
| | | fileShareDialogVisible.value = true; |
| | | }; |
| | | |
| | | // 手动刷新数据 |
| | | const manualRefresh = () => { |
| | | generateNewData(); |
| | | getList(); |
| | | ElMessage.success("手动刷新完成,已生成新通知"); |
| | | }; |
| | | |
| | | // 提交通知表单 |
| | | const submitForm = async () => { |
| | | try { |
| | | await formRef.value.validate(); |
| | | |
| | | if (dialogType.value === "add") { |
| | | // 新增通知 |
| | | addNotification({...form.value}).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("添加成功"); |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | } else { |
| | | // 编辑通知 |
| | | updateNotification({...form.value}).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("更新成功"); |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | } |
| | | } catch (error) { |
| | | console.error("表单验证失败:", error); |
| | | } |
| | | }; |
| | | |
| | | // 创建会议 |
| | | const createMeeting = async () => { |
| | | try { |
| | | await meetingFormRef.value.validate(); |
| | | |
| | | // 模拟创建会议 |
| | | const meetingInfo = { |
| | | title: meetingForm.value.title, |
| | | startTime: meetingForm.value.startTime, |
| | | duration: meetingForm.value.duration, |
| | | participants: meetingForm.value.participants, |
| | | description: meetingForm.value.description, |
| | | platform: meetingForm.value.platform |
| | | }; |
| | | // 新增会议 |
| | | addOnlineMeeting({...meetingInfo}).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("会议添加成功"); |
| | | meetingDialogVisible.value = false; |
| | | getList(); |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | // 模拟发送到企业微信/钉钉 |
| | | // const platformName = meetingPlatforms.find(p => p.value === meetingForm.value.platform)?.label || "未知平台"; |
| | | // ElMessage.success(`会议创建成功!会议ID: ${meetingInfo.meetingId},将通过${platformName}发送通知`); |
| | | |
| | | // 获取参会人员信息 |
| | | const participantNames = meetingForm.value.participants.map(participantId => { |
| | | const employee = employees.value.find(emp => emp.value === participantId); |
| | | return employee ? employee.label : '未知人员'; |
| | | }).join('、'); |
| | | |
| | | // 获取参会人员详细信息 |
| | | const participantDetails = meetingForm.value.participants.map(participantId => { |
| | | const employee = employees.value.find(emp => emp.value === participantId); |
| | | return employee ? { |
| | | name: employee.label, |
| | | dept: employee.dept, |
| | | phone: employee.phone, |
| | | email: employee.email |
| | | } : null; |
| | | }).filter(Boolean); |
| | | |
| | | // 将会议信息添加到通知列表 |
| | | const meetingNotification = { |
| | | title: `[会议通知] ${meetingInfo.title}`, |
| | | type: "meeting", |
| | | priority: "high", |
| | | status: "published", |
| | | content: `会议时间: ${meetingInfo.startTime},时长: ${meetingInfo.duration}分钟,平台: ${meetingPlatforms.find(p => p.value === meetingForm.value.platform)?.label || "未知平台"},参会人员: ${participantNames},共${participantDetails.length}人`, |
| | | departments: [], |
| | | expireDate: "", |
| | | syncMethods: [meetingForm.value.platform] |
| | | }; |
| | | addNotification({...meetingNotification}).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("添加成功"); |
| | | // dialogVisible.value = false; |
| | | getList(); |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | // mockData.unshift(meetingNotification); |
| | | // getList(); |
| | | } catch (error) { |
| | | console.error("会议表单验证失败:", error); |
| | | } |
| | | }; |
| | | |
| | | // 文件上传处理 |
| | | const handleFileChange = (file) => { |
| | | const isLt10M = file.size / 1024 / 1024 < 10; |
| | | if (!isLt10M) { |
| | | ElMessage.error("上传文件大小不能超过 10MB!"); |
| | | return false; |
| | | } |
| | | |
| | | const fileInfo = { |
| | | name: file.name, |
| | | size: file.size, |
| | | type: file.type, |
| | | uid: file.uid |
| | | }; |
| | | |
| | | fileList.value.push(fileInfo); |
| | | fileShareForm.value.files.push(fileInfo.name); |
| | | return false; // 阻止自动上传 |
| | | }; |
| | | |
| | | // 移除文件 |
| | | const removeFile = (file) => { |
| | | const index = fileList.value.findIndex(item => item.uid === file.uid); |
| | | if (index !== -1) { |
| | | const index2 = fileShareForm.value.files.findIndex(item => item.uid === file.uid); |
| | | if (index2 !== -1) { |
| | | fileShareForm.value.files.splice(index2, 1); |
| | | } |
| | | fileList.value.splice(index, 1); |
| | | } |
| | | }; |
| | | |
| | | // 共享文件 |
| | | const shareFiles = async () => { |
| | | try { |
| | | await fileShareFormRef.value.validate(); |
| | | |
| | | if (fileShareForm.value.files.length === 0) { |
| | | ElMessage.warning("请至少选择一个文件"); |
| | | return; |
| | | } |
| | | |
| | | // 模拟文件共享 |
| | | const shareInfo = { |
| | | title: fileShareForm.value.title, |
| | | description: fileShareForm.value.description, |
| | | departments: fileShareForm.value.departments, |
| | | files: fileShareForm.value.files, |
| | | }; |
| | | addFileSharing({...shareInfo}).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("文件共享成功"); |
| | | fileShareDialogVisible.value = false; |
| | | getList(); |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | |
| | | // ElMessage.success(`文件共享成功!共享ID: ${shareInfo.shareId},已通知相关部门`); |
| | | |
| | | |
| | | // 将文件共享信息添加到通知列表 |
| | | const fileShareNotification = { |
| | | title: `[文件共享] ${shareInfo.title}`, |
| | | type: "temporary", |
| | | priority: "medium", |
| | | status: "published", |
| | | content: `共享描述: ${shareInfo.description},文件数量: ${shareInfo.files.length}个`, |
| | | departments: shareInfo.departments, |
| | | expireDate: "", |
| | | syncMethods: ["wechat", "dingtalk"], |
| | | }; |
| | | addNotification({...fileShareNotification}).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("添加成功"); |
| | | // dialogVisible.value = false; |
| | | getList(); |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | |
| | | // mockData.unshift(fileShareNotification); |
| | | // getList(); |
| | | } catch (error) { |
| | | console.error("文件共享表单验证失败:", error); |
| | | } |
| | | }; |
| | | |
| | | // 发布通知 |
| | | const publishNotification = (row) => { |
| | | Object.assign(form.value, { |
| | | id: row.id, |
| | | title: row.title, |
| | | type: row.type, |
| | | priority: row.priority, |
| | | content: row.content || "", |
| | | departments: row.departments || [], |
| | | expireDate: row.expireDate || "", |
| | | status: row.status, |
| | | syncMethods: row.syncMethods || [] |
| | | }); |
| | | form.value.status = "published"; |
| | | updateNotification({...form.value}).then(res => { |
| | | if(res.code == 200){ |
| | | form.value.status = "published"; |
| | | updateNotification({ ...form.value }) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("通知发布成功"); |
| | | getList(); |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | }; |
| | | .catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | }; |
| | | |
| | | // 撤回通知 |
| | | const revokeNotification = (row) => { |
| | | // 撤回通知 |
| | | const revokeNotification = row => { |
| | | Object.assign(form.value, { |
| | | id: row.id, |
| | | title: row.title, |
| | | type: row.type, |
| | | priority: row.priority, |
| | | content: row.content || "", |
| | | departments: row.departments || [], |
| | | expireDate: row.expireDate || "", |
| | | status: row.status, |
| | | syncMethods: row.syncMethods || [] |
| | | }); |
| | | form.value.status = "draft"; |
| | | updateNotification({...form.value}).then(res => { |
| | | if(res.code == 200){ |
| | | id: row.id, |
| | | title: row.title, |
| | | type: row.type, |
| | | priority: row.priority, |
| | | content: row.content || "", |
| | | departments: row.departments || [], |
| | | expireDate: row.expireDate || "", |
| | | status: row.status, |
| | | syncMethods: row.syncMethods || [], |
| | | }); |
| | | form.value.status = "draft"; |
| | | updateNotification({ ...form.value }) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("通知已撤回"); |
| | | getList(); |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | }; |
| | | .catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | }; |
| | | |
| | | // 删除通知 |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedIds.value.length > 0) { |
| | | ids = selectedIds.value; |
| | | }else{ |
| | | ElMessage.warning("请选择要删除的通知"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }).then(() => { |
| | | delNotification(ids).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("删除成功"); |
| | | selectedIds.value = []; |
| | | getList(); |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | // 删除通知 |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedIds.value.length > 0) { |
| | | ids = selectedIds.value; |
| | | } else { |
| | | ElMessage.warning("请选择要删除的通知"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }) |
| | | }).catch(() => { |
| | | // 用户取消 |
| | | }); |
| | | }; |
| | | .then(() => { |
| | | delNotification(ids) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("删除成功"); |
| | | selectedIds.value = []; |
| | | getList(); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | // 用户取消 |
| | | }); |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .auto-refresh-info { |
| | | margin-bottom: 15px; |
| | | } |
| | | .auto-refresh-info { |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .auto-refresh-info .el-alert { |
| | | border-radius: 8px; |
| | | } |
| | | .auto-refresh-info .el-alert { |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | |
| | | .el-upload__tip { |
| | | color: #909399; |
| | | font-size: 12px; |
| | | margin-top: 8px; |
| | | } |
| | | .el-upload__tip { |
| | | color: #909399; |
| | | font-size: 12px; |
| | | margin-top: 8px; |
| | | } |
| | | |
| | | .el-checkbox-group { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 10px; |
| | | } |
| | | .el-checkbox-group { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .el-checkbox { |
| | | margin-right: 0; |
| | | } |
| | | .el-checkbox { |
| | | margin-right: 0; |
| | | } |
| | | </style> |
| | |
| | | <!-- 页面标题 --> |
| | | <div class="page-header"> |
| | | <h2>会议草稿</h2> |
| | | <el-button type="primary" @click="handleAdd"> |
| | | <el-icon><Plus /></el-icon> |
| | | <el-button type="primary" |
| | | @click="handleAdd"> |
| | | <el-icon> |
| | | <Plus /> |
| | | </el-icon> |
| | | 新建草稿 |
| | | </el-button> |
| | | </div> |
| | | |
| | | <!-- 搜索区域 --> |
| | | <el-card class="search-card"> |
| | | <el-form :model="searchForm" label-width="100px" inline> |
| | | <el-form :model="searchForm" |
| | | label-width="100px" |
| | | inline> |
| | | <el-form-item label="会议主题"> |
| | | <el-input v-model="searchForm.title" placeholder="请输入会议主题" clearable /> |
| | | <el-input v-model="searchForm.title" |
| | | placeholder="请输入会议主题" |
| | | clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="会议日期"> |
| | | <el-date-picker |
| | | v-model="searchForm.meetingDate" |
| | | type="date" |
| | | placeholder="请选择会议日期" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | style="width: 100%" |
| | | /> |
| | | <el-date-picker v-model="searchForm.meetingDate" |
| | | type="date" |
| | | placeholder="请选择会议日期" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch">搜索</el-button> |
| | | <el-button type="primary" |
| | | @click="handleSearch">搜索</el-button> |
| | | <el-button @click="resetSearch">重置</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | | |
| | | <!-- 草稿列表 --> |
| | | <el-card> |
| | | <el-table v-loading="loading" :data="draftList" border> |
| | | <el-table-column prop="title" label="会议主题" align="center" min-width="200" show-overflow-tooltip /> |
| | | <el-table-column prop="room" label="会议室" align="center" width="120" /> |
| | | <el-table-column prop="host" label="主持人" align="center" width="120" /> |
| | | <el-table-column prop="meetingTime" label="会议时间" align="center" width="180"> |
| | | <el-table v-loading="loading" |
| | | :data="draftList" |
| | | border> |
| | | <el-table-column prop="title" |
| | | label="会议主题" |
| | | align="center" |
| | | min-width="200" |
| | | show-overflow-tooltip /> |
| | | <el-table-column prop="room" |
| | | label="会议室" |
| | | align="center" |
| | | width="120" /> |
| | | <el-table-column prop="host" |
| | | label="主持人" |
| | | align="center" |
| | | width="120" /> |
| | | <el-table-column prop="meetingTime" |
| | | label="会议时间" |
| | | align="center" |
| | | width="180"> |
| | | <template #default="scope"> |
| | | {{ formatDateTime(scope.row.meetingTime) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="participants" label="参会人数" align="center" width="100"> |
| | | <el-table-column prop="participants" |
| | | label="参会人数" |
| | | align="center" |
| | | width="100"> |
| | | <template #default="scope"> |
| | | {{ scope.row.participants }}人 |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="createTime" label="创建时间" align="center" width="180" /> |
| | | <el-table-column label="操作" align="center" width="200" fixed="right"> |
| | | <el-table-column prop="createTime" |
| | | label="创建时间" |
| | | align="center" |
| | | width="180" /> |
| | | <el-table-column label="操作" |
| | | align="center" |
| | | width="200" |
| | | fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button type="primary" link @click="viewDraft(scope.row)">查看</el-button> |
| | | <el-button type="primary" link @click="editDraft(scope.row)">编辑</el-button> |
| | | <el-button type="danger" link @click="deleteDraft(scope.row)">删除</el-button> |
| | | <el-button type="primary" |
| | | link |
| | | @click="viewDraft(scope.row)">查看</el-button> |
| | | <el-button type="primary" |
| | | link |
| | | @click="editDraft(scope.row)">编辑</el-button> |
| | | <el-button type="danger" |
| | | link |
| | | @click="deleteDraft(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" |
| | | /> |
| | | <pagination v-show="total > 0" |
| | | :total="total" |
| | | v-model:page="queryParams.current" |
| | | v-model:limit="queryParams.size" |
| | | @pagination="getList" /> |
| | | </el-card> |
| | | |
| | | <!-- 会议草稿详情对话框 --> |
| | | <el-dialog |
| | | title="会议草稿详情" |
| | | v-model="detailDialogVisible" |
| | | width="800px" |
| | | > |
| | | <el-dialog title="会议草稿详情" |
| | | v-model="detailDialogVisible" |
| | | width="800px"> |
| | | <div v-if="currentDraft"> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions :column="2" |
| | | border> |
| | | <el-descriptions-item label="会议主题">{{ currentDraft.title }}</el-descriptions-item> |
| | | <el-descriptions-item label="会议编号">{{ currentDraft.meetingId }}</el-descriptions-item> |
| | | <el-descriptions-item label="会议室">{{ currentDraft.room }}</el-descriptions-item> |
| | | <el-descriptions-item label="主持人">{{ currentDraft.host }}</el-descriptions-item> |
| | | <el-descriptions-item label="会议时间" :span="2"> |
| | | <el-descriptions-item label="会议时间" |
| | | :span="2"> |
| | | {{ formatDateTime(currentDraft.meetingTime) }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="创建时间">{{ currentDraft.createTime }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div class="content-section mt-20"> |
| | | <h4>参会人员</h4> |
| | | <div class="participants-list"> |
| | | {{ currentDraft.participantList }} |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="content-section mt-20"> |
| | | <h4>会议说明</h4> |
| | | <div class="meeting-description">{{ currentDraft.description }}</div> |
| | | </div> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="detailDialogVisible = false">关 闭</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 新建/编辑草稿对话框 --> |
| | | <el-dialog |
| | | :title="dialogTitle" |
| | | v-model="editDialogVisible" |
| | | width="700px" |
| | | > |
| | | <el-form :model="meetingForm" :rules="rules" ref="meetingFormRef" label-width="100px"> |
| | | <el-form-item label="会议主题" prop="title"> |
| | | <el-input v-model="meetingForm.title" placeholder="请输入会议主题" /> |
| | | <el-dialog :title="dialogTitle" |
| | | v-model="editDialogVisible" |
| | | width="700px"> |
| | | <el-form :model="meetingForm" |
| | | :rules="rules" |
| | | ref="meetingFormRef" |
| | | label-width="100px"> |
| | | <el-form-item label="会议主题" |
| | | prop="title"> |
| | | <el-input v-model="meetingForm.title" |
| | | placeholder="请输入会议主题" /> |
| | | </el-form-item> |
| | | <el-form-item label="会议室" prop="room"> |
| | | <el-select v-model="meetingForm.roomId" placeholder="请选择会议室" style="width: 100%"> |
| | | <el-option v-for="(v,k) in roomList" :label="v.name" :value="v.id" /> |
| | | <el-form-item label="会议室" |
| | | prop="room"> |
| | | <el-select v-model="meetingForm.roomId" |
| | | placeholder="请选择会议室" |
| | | style="width: 100%"> |
| | | <el-option v-for="(v,k) in roomList" |
| | | :label="v.name" |
| | | :value="v.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="主持人" prop="host"> |
| | | <el-input v-model="meetingForm.host" placeholder="请输入主持人" /> |
| | | <el-form-item label="主持人" |
| | | prop="host"> |
| | | <el-input v-model="meetingForm.host" |
| | | placeholder="请输入主持人" /> |
| | | </el-form-item> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="会议日期" prop="meetingDate"> |
| | | <el-date-picker |
| | | v-model="meetingForm.meetingDate" |
| | | type="date" |
| | | placeholder="请选择会议日期" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | :disabled-date="disabledDate" |
| | | style="width: 100%" |
| | | /> |
| | | <el-form-item label="会议日期" |
| | | prop="meetingDate"> |
| | | <el-date-picker v-model="meetingForm.meetingDate" |
| | | type="date" |
| | | placeholder="请选择会议日期" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | :disabled-date="disabledDate" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="开始时间" prop="startTime"> |
| | | <el-select |
| | | v-model="meetingForm.startTime" |
| | | placeholder="请选择开始时间" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="time in timeOptions" |
| | | :key="time.value" |
| | | :label="time.label" |
| | | :value="time.value" |
| | | /> |
| | | <el-form-item label="开始时间" |
| | | prop="startTime"> |
| | | <el-select v-model="meetingForm.startTime" |
| | | placeholder="请选择开始时间" |
| | | style="width: 100%"> |
| | | <el-option v-for="time in timeOptions" |
| | | :key="time.value" |
| | | :label="time.label" |
| | | :value="time.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="结束时间" prop="endTime"> |
| | | <el-select |
| | | v-model="meetingForm.endTime" |
| | | placeholder="请选择结束时间" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="time in timeOptions" |
| | | :key="time.value" |
| | | :label="time.label" |
| | | :value="time.value" |
| | | /> |
| | | <el-form-item label="结束时间" |
| | | prop="endTime"> |
| | | <el-select v-model="meetingForm.endTime" |
| | | placeholder="请选择结束时间" |
| | | style="width: 100%"> |
| | | <el-option v-for="time in timeOptions" |
| | | :key="time.value" |
| | | :label="time.label" |
| | | :value="time.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="参会人数" prop="participants"> |
| | | <el-input |
| | | v-model="meetingForm.participants" |
| | | type="number" |
| | | placeholder="请输入参会人数" |
| | | /> |
| | | <el-form-item label="参会人数" |
| | | prop="participants"> |
| | | <el-input v-model="meetingForm.participants" |
| | | type="number" |
| | | placeholder="请输入参会人数" /> |
| | | </el-form-item> |
| | | <el-form-item label="参会人员" prop="participants"> |
| | | <el-input |
| | | v-model="meetingForm.participantList" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请输入参会人员,用逗号分隔" |
| | | /> |
| | | <el-form-item label="参会人员" |
| | | prop="participants"> |
| | | <el-input v-model="meetingForm.participantList" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请输入参会人员,用逗号分隔" /> |
| | | </el-form-item> |
| | | <el-form-item label="会议说明"> |
| | | <el-input |
| | | v-model="meetingForm.description" |
| | | type="textarea" |
| | | :rows="4" |
| | | placeholder="请输入会议说明" |
| | | /> |
| | | <el-input v-model="meetingForm.description" |
| | | type="textarea" |
| | | :rows="4" |
| | | placeholder="请输入会议说明" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="submitForm">保 存</el-button> |
| | | <el-button @click="editDialogVisible = false">取 消</el-button> |
| | | <el-button type="primary" @click="submitForm">保 存</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { Plus } from '@element-plus/icons-vue' |
| | | import Pagination from '@/components/Pagination/index.vue' |
| | | import {getRoomEnum,getDraftList,saveDraft,delDraft} from '@/api/collaborativeApproval/meeting.js' |
| | | import dayjs from "dayjs"; |
| | | // 数据列表加载状态 |
| | | const loading = ref(false) |
| | | import { ref, reactive, onMounted } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import { Plus } from "@element-plus/icons-vue"; |
| | | import Pagination from "@/components/Pagination/index.vue"; |
| | | import { |
| | | getRoomEnum, |
| | | getDraftList, |
| | | saveDraft, |
| | | delDraft, |
| | | } from "@/api/collaborativeApproval/meeting.js"; |
| | | import dayjs from "dayjs"; |
| | | // 数据列表加载状态 |
| | | const loading = ref(false); |
| | | |
| | | // 总条数 |
| | | const total = ref(0) |
| | | // 总条数 |
| | | const total = ref(0); |
| | | |
| | | // 草稿列表数据 |
| | | const draftList = ref([]) |
| | | // 草稿列表数据 |
| | | const draftList = ref([]); |
| | | |
| | | // 查询参数 |
| | | const queryParams = reactive({ |
| | | current: 1, |
| | | size: 10 |
| | | }) |
| | | // 查询参数 |
| | | const queryParams = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | }); |
| | | |
| | | // 搜索表单 |
| | | const searchForm = reactive({ |
| | | title: '', |
| | | meetingDate: '' |
| | | }) |
| | | // 搜索表单 |
| | | const searchForm = reactive({ |
| | | title: "", |
| | | meetingDate: "", |
| | | }); |
| | | |
| | | // 是否显示对话框 |
| | | const detailDialogVisible = ref(false) |
| | | const editDialogVisible = ref(false) |
| | | // 是否显示对话框 |
| | | const detailDialogVisible = ref(false); |
| | | const editDialogVisible = ref(false); |
| | | |
| | | const roomList = ref([]) |
| | | const roomList = ref([]); |
| | | |
| | | // 对话框标题 |
| | | const dialogTitle = ref('') |
| | | // 对话框标题 |
| | | const dialogTitle = ref(""); |
| | | |
| | | // 当前查看的草稿 |
| | | const currentDraft = ref(null) |
| | | // 当前查看的草稿 |
| | | const currentDraft = ref(null); |
| | | |
| | | // 表单引用 |
| | | const meetingFormRef = ref(null) |
| | | // 表单引用 |
| | | const meetingFormRef = ref(null); |
| | | |
| | | // 时间选项(以半小时为间隔,工作时间8:00-18:00) |
| | | const timeOptions = ref([]) |
| | | // 时间选项(以半小时为间隔,工作时间8:00-18:00) |
| | | const timeOptions = ref([]); |
| | | |
| | | // 表单数据 |
| | | const meetingForm = reactive({ |
| | | id: '', |
| | | meetingId: '', |
| | | title: '', |
| | | roomId: '', |
| | | host: '', |
| | | meetingDate: '', |
| | | startTime: '', |
| | | endTime: '', |
| | | participants: 0, |
| | | participantList: '', |
| | | description: '', |
| | | createTime: '' |
| | | }) |
| | | |
| | | // 表单校验规则 |
| | | const rules = { |
| | | title: [{ required: true, message: '请输入会议主题', trigger: 'blur' }], |
| | | roomId: [{ required: true, message: '请选择会议室', trigger: 'change' }], |
| | | host: [{ required: true, message: '请输入主持人', trigger: 'blur' }], |
| | | meetingDate: [{ required: true, message: '请选择会议日期', trigger: 'change' }], |
| | | startTime: [{ required: true, message: '请选择开始时间', trigger: 'change' }], |
| | | endTime: [{ required: true, message: '请选择结束时间', trigger: 'change' }] |
| | | } |
| | | |
| | | // 初始化时间选项(以半小时为间隔,工作时间8:00-18:00) |
| | | const initTimeOptions = () => { |
| | | const options = [] |
| | | for (let hour = 8; hour <= 18; hour++) { |
| | | // 每个小时添加两个选项:整点和半点 |
| | | options.push({ |
| | | value: `${hour.toString().padStart(2, '0')}:00`, |
| | | label: `${hour.toString().padStart(2, '0')}:00` |
| | | }) |
| | | |
| | | if (hour < 18) { // 18:00之后没有半点选项 |
| | | options.push({ |
| | | value: `${hour.toString().padStart(2, '0')}:30`, |
| | | label: `${hour.toString().padStart(2, '0')}:30` |
| | | }) |
| | | } |
| | | } |
| | | timeOptions.value = options |
| | | } |
| | | |
| | | // 禁用日期(禁用今天之前的日期) |
| | | const disabledDate = (time) => { |
| | | // 禁用今天之前的日期 |
| | | return time.getTime() < Date.now() - 86400000 |
| | | } |
| | | |
| | | // 查询数据 |
| | | const getList = async () => { |
| | | loading.value = true |
| | | |
| | | let resp = await getDraftList({...queryParams,...searchForm}) |
| | | queryParams.current = resp.data.current |
| | | draftList.value = resp.data.records.map(it=>{ |
| | | it.room = roomList.value.find(room=>it.roomId===room.id).name ?? "" |
| | | it.meetingTime = `${it.meetingDate} ${dayjs(it.startTime).format("HH:mm")} ~ ${dayjs(it.endTime).format("HH:mm")}` |
| | | return it |
| | | }) |
| | | |
| | | loading.value = false |
| | | |
| | | } |
| | | |
| | | // 搜索按钮操作 |
| | | const handleSearch = () => { |
| | | queryParams.pageNum = 1 |
| | | getList() |
| | | } |
| | | |
| | | // 重置搜索表单 |
| | | const resetSearch = () => { |
| | | Object.assign(searchForm, { |
| | | title: '', |
| | | createTime: [] |
| | | }) |
| | | handleSearch() |
| | | } |
| | | |
| | | // 添加按钮操作 |
| | | const handleAdd = () => { |
| | | dialogTitle.value = '新建草稿' |
| | | resetForm() |
| | | editDialogVisible.value = true |
| | | } |
| | | |
| | | // 查看草稿详情 |
| | | const viewDraft = (row) => { |
| | | currentDraft.value = row |
| | | detailDialogVisible.value = true |
| | | } |
| | | |
| | | // 编辑草稿 |
| | | const editDraft = (row) => { |
| | | dialogTitle.value = '编辑草稿' |
| | | Object.assign(meetingForm, { |
| | | id: row.id, |
| | | meetingId: row.meetingId, |
| | | title: row.title, |
| | | room: row.room, |
| | | roomId: row.id, |
| | | host: row.host, |
| | | meetingDate: row.meetingTime.split(' ')[0], |
| | | startTime: row.meetingTime.split(' ')[1], |
| | | endTime: row.meetingTime.split(' ')[3], |
| | | participants: row.participants, |
| | | participantList: row.participantList, |
| | | description: row.description, |
| | | createTime: row.createTime |
| | | }) |
| | | editDialogVisible.value = true |
| | | } |
| | | |
| | | // 删除草稿 |
| | | const deleteDraft = (row) => { |
| | | ElMessageBox.confirm( |
| | | `确认删除会议草稿 "${row.title}"?`, |
| | | '删除草稿', |
| | | { |
| | | confirmButtonText: '确定', |
| | | cancelButtonText: '取消', |
| | | type: 'warning' |
| | | } |
| | | ).then(() => { |
| | | delDraft(row.id).then(resp=>{ |
| | | ElMessage.success('草稿删除成功') |
| | | getList() |
| | | }) |
| | | |
| | | }).catch(() => {}) |
| | | } |
| | | |
| | | // 重置表单 |
| | | const resetForm = () => { |
| | | Object.assign(meetingForm, { |
| | | id: '', |
| | | meetingId: '', |
| | | title: '', |
| | | room: '', |
| | | host: '', |
| | | meetingDate: '', |
| | | startTime: '', |
| | | endTime: '', |
| | | // 表单数据 |
| | | const meetingForm = reactive({ |
| | | id: "", |
| | | meetingId: "", |
| | | title: "", |
| | | roomId: "", |
| | | host: "", |
| | | meetingDate: "", |
| | | startTime: "", |
| | | endTime: "", |
| | | participants: 0, |
| | | participantList: '', |
| | | description: '', |
| | | createTime: '' |
| | | }) |
| | | } |
| | | participantList: "", |
| | | description: "", |
| | | createTime: "", |
| | | }); |
| | | |
| | | // 提交表单 |
| | | const submitForm = () => { |
| | | meetingFormRef.value.validate((valid) => { |
| | | if (valid) { |
| | | let formData = {...meetingForm} |
| | | formData.startTime = dayjs(meetingForm.meetingDate + ' ' + meetingForm.startTime).format("YYYY-MM-DD HH:mm:ss") |
| | | formData.endTime = dayjs(meetingForm.meetingDate + ' ' + meetingForm.endTime).format("YYYY-MM-DD HH:mm:ss") |
| | | saveDraft(formData).then(()=>{ |
| | | ElMessage.success('保存成功') |
| | | editDialogVisible.value = false |
| | | getList() |
| | | }) |
| | | // 表单校验规则 |
| | | const rules = { |
| | | title: [{ required: true, message: "请输入会议主题", trigger: "blur" }], |
| | | roomId: [{ required: true, message: "请选择会议室", trigger: "change" }], |
| | | host: [{ required: true, message: "请输入主持人", trigger: "blur" }], |
| | | meetingDate: [ |
| | | { required: true, message: "请选择会议日期", trigger: "change" }, |
| | | ], |
| | | startTime: [{ required: true, message: "请选择开始时间", trigger: "change" }], |
| | | endTime: [{ required: true, message: "请选择结束时间", trigger: "change" }], |
| | | }; |
| | | |
| | | // 初始化时间选项(以半小时为间隔,工作时间8:00-18:00) |
| | | const initTimeOptions = () => { |
| | | const options = []; |
| | | for (let hour = 8; hour <= 18; hour++) { |
| | | // 每个小时添加两个选项:整点和半点 |
| | | options.push({ |
| | | value: `${hour.toString().padStart(2, "0")}:00`, |
| | | label: `${hour.toString().padStart(2, "0")}:00`, |
| | | }); |
| | | |
| | | if (hour < 18) { |
| | | // 18:00之后没有半点选项 |
| | | options.push({ |
| | | value: `${hour.toString().padStart(2, "0")}:30`, |
| | | label: `${hour.toString().padStart(2, "0")}:30`, |
| | | }); |
| | | } |
| | | } |
| | | }) |
| | | } |
| | | timeOptions.value = options; |
| | | }; |
| | | |
| | | // 格式化日期时间 |
| | | const formatDateTime = (dateTime) => { |
| | | if (!dateTime) return '' |
| | | return dateTime.replace(' ', '\n') |
| | | } |
| | | // 禁用日期(禁用今天之前的日期) |
| | | const disabledDate = time => { |
| | | // 禁用今天之前的日期 |
| | | return time.getTime() < Date.now() - 86400000; |
| | | }; |
| | | |
| | | // 页面加载时获取数据 |
| | | onMounted(() => { |
| | | initTimeOptions() |
| | | getList() |
| | | getRoomEnum().then((res) => { |
| | | roomList.value = res.data |
| | | }) |
| | | }) |
| | | // 查询数据 |
| | | const getList = async () => { |
| | | loading.value = true; |
| | | |
| | | let resp = await getDraftList({ ...queryParams, ...searchForm }); |
| | | queryParams.current = resp.data.current; |
| | | draftList.value = resp.data.records.map(it => { |
| | | it.room = roomList.value.find(room => it.roomId === room.id).name ?? ""; |
| | | it.meetingTime = `${it.meetingDate} ${dayjs(it.startTime).format( |
| | | "HH:mm" |
| | | )} ~ ${dayjs(it.endTime).format("HH:mm")}`; |
| | | return it; |
| | | }); |
| | | |
| | | loading.value = false; |
| | | }; |
| | | |
| | | // 搜索按钮操作 |
| | | const handleSearch = () => { |
| | | queryParams.pageNum = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | // 重置搜索表单 |
| | | const resetSearch = () => { |
| | | Object.assign(searchForm, { |
| | | title: "", |
| | | createTime: [], |
| | | }); |
| | | handleSearch(); |
| | | }; |
| | | |
| | | // 添加按钮操作 |
| | | const handleAdd = () => { |
| | | dialogTitle.value = "新建草稿"; |
| | | resetForm(); |
| | | editDialogVisible.value = true; |
| | | }; |
| | | |
| | | // 查看草稿详情 |
| | | const viewDraft = row => { |
| | | currentDraft.value = row; |
| | | detailDialogVisible.value = true; |
| | | }; |
| | | |
| | | // 编辑草稿 |
| | | const editDraft = row => { |
| | | dialogTitle.value = "编辑草稿"; |
| | | Object.assign(meetingForm, { |
| | | id: row.id, |
| | | meetingId: row.meetingId, |
| | | title: row.title, |
| | | room: row.room, |
| | | roomId: row.id, |
| | | host: row.host, |
| | | meetingDate: row.meetingTime.split(" ")[0], |
| | | startTime: row.meetingTime.split(" ")[1], |
| | | endTime: row.meetingTime.split(" ")[3], |
| | | participants: row.participants, |
| | | participantList: row.participantList, |
| | | description: row.description, |
| | | createTime: row.createTime, |
| | | }); |
| | | editDialogVisible.value = true; |
| | | }; |
| | | |
| | | // 删除草稿 |
| | | const deleteDraft = row => { |
| | | ElMessageBox.confirm(`确认删除会议草稿 "${row.title}"?`, "删除草稿", { |
| | | confirmButtonText: "确定", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | delDraft(row.id).then(resp => { |
| | | ElMessage.success("草稿删除成功"); |
| | | getList(); |
| | | }); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | // 重置表单 |
| | | const resetForm = () => { |
| | | Object.assign(meetingForm, { |
| | | id: "", |
| | | meetingId: "", |
| | | title: "", |
| | | room: "", |
| | | host: "", |
| | | meetingDate: "", |
| | | startTime: "", |
| | | endTime: "", |
| | | participants: 0, |
| | | participantList: "", |
| | | description: "", |
| | | createTime: "", |
| | | }); |
| | | }; |
| | | |
| | | // 提交表单 |
| | | const submitForm = () => { |
| | | meetingFormRef.value.validate(valid => { |
| | | if (valid) { |
| | | let formData = { ...meetingForm }; |
| | | formData.startTime = dayjs( |
| | | meetingForm.meetingDate + " " + meetingForm.startTime |
| | | ).format("YYYY-MM-DD HH:mm:ss"); |
| | | formData.endTime = dayjs( |
| | | meetingForm.meetingDate + " " + meetingForm.endTime |
| | | ).format("YYYY-MM-DD HH:mm:ss"); |
| | | saveDraft(formData).then(() => { |
| | | ElMessage.success("保存成功"); |
| | | editDialogVisible.value = false; |
| | | getList(); |
| | | }); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // 格式化日期时间 |
| | | const formatDateTime = dateTime => { |
| | | if (!dateTime) return ""; |
| | | return dateTime.replace(" ", "\n"); |
| | | }; |
| | | |
| | | // 页面加载时获取数据 |
| | | onMounted(() => { |
| | | initTimeOptions(); |
| | | getList(); |
| | | getRoomEnum().then(res => { |
| | | roomList.value = res.data; |
| | | }); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .page-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | } |
| | | .page-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | margin: 0; |
| | | color: #303133; |
| | | } |
| | | .page-header h2 { |
| | | margin: 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .search-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | .search-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .content-section h4 { |
| | | margin: 0 0 15px 0; |
| | | color: #303133; |
| | | } |
| | | .content-section h4 { |
| | | margin: 0 0 15px 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .mt-20 { |
| | | margin-top: 20px; |
| | | } |
| | | .mt-20 { |
| | | margin-top: 20px; |
| | | } |
| | | |
| | | .participants-list { |
| | | min-height: 40px; |
| | | padding: 15px; |
| | | border-radius: 4px; |
| | | line-height: 1.6; |
| | | } |
| | | .participants-list { |
| | | min-height: 40px; |
| | | padding: 15px; |
| | | border-radius: 4px; |
| | | line-height: 1.6; |
| | | } |
| | | |
| | | .meeting-description { |
| | | padding: 15px; |
| | | border-radius: 4px; |
| | | line-height: 1.6; |
| | | white-space: pre-wrap; |
| | | } |
| | | .meeting-description { |
| | | padding: 15px; |
| | | border-radius: 4px; |
| | | line-height: 1.6; |
| | | white-space: pre-wrap; |
| | | } |
| | | </style> |
| | |
| | | <div class="page-header"> |
| | | <h2>会议发布</h2> |
| | | </div> |
| | | |
| | | <!-- 搜索区域 --> |
| | | <el-card class="search-card"> |
| | | <el-form :model="searchForm" inline> |
| | | <el-form :model="searchForm" |
| | | inline> |
| | | <el-form-item label="会议主题"> |
| | | <el-input v-model="searchForm.title" placeholder="请输入会议主题" clearable/> |
| | | <el-input v-model="searchForm.title" |
| | | placeholder="请输入会议主题" |
| | | clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="申请人"> |
| | | <el-input v-model="searchForm.applicant" placeholder="请输入申请人" clearable/> |
| | | <el-input v-model="searchForm.applicant" |
| | | placeholder="请输入申请人" |
| | | clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="发布状态"> |
| | | <el-select style="width: 100px" v-model="searchForm.status" placeholder="请选择发布状态" clearable> |
| | | <el-option label="待发布" value="0"/> |
| | | <el-option label="已发布" value="1"/> |
| | | <el-select style="width: 100px" |
| | | v-model="searchForm.status" |
| | | placeholder="请选择发布状态" |
| | | clearable> |
| | | <el-option label="待发布" |
| | | value="0" /> |
| | | <el-option label="已发布" |
| | | value="1" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch">搜索</el-button> |
| | | <el-button type="primary" |
| | | @click="handleSearch">搜索</el-button> |
| | | <el-button @click="resetSearch">重置</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | | |
| | | <!-- 会议发布列表 --> |
| | | <el-card> |
| | | <el-table v-loading="loading" :data="approvalList" border> |
| | | <el-table-column prop="title" label="会议主题" align="center" min-width="200" show-overflow-tooltip/> |
| | | <el-table-column prop="applicant" label="申请人" align="center" width="120"/> |
| | | <el-table-column prop="host" label="主理人" align="center" width="120"/> |
| | | <el-table-column prop="meetingTime" label="会议时间" align="center" width="180"> |
| | | <el-table v-loading="loading" |
| | | :data="approvalList" |
| | | border> |
| | | <el-table-column prop="title" |
| | | label="会议主题" |
| | | align="center" |
| | | min-width="200" |
| | | show-overflow-tooltip /> |
| | | <el-table-column prop="applicant" |
| | | label="申请人" |
| | | align="center" |
| | | width="120" /> |
| | | <el-table-column prop="host" |
| | | label="主理人" |
| | | align="center" |
| | | width="120" /> |
| | | <el-table-column prop="meetingTime" |
| | | label="会议时间" |
| | | align="center" |
| | | width="180"> |
| | | <template #default="scope"> |
| | | {{ formatDateTime(scope.row.meetingTime) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="location" label="会议地点" align="center" width="150"/> |
| | | <el-table-column prop="participants" label="参会人数" align="center" width="100"> |
| | | <el-table-column prop="location" |
| | | label="会议地点" |
| | | align="center" |
| | | width="150" /> |
| | | <el-table-column prop="participants" |
| | | label="参会人数" |
| | | align="center" |
| | | width="100"> |
| | | <template #default="scope"> |
| | | {{ scope.row.participants.length }}人 |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="status" label="发布状态" align="center" width="120"> |
| | | <el-table-column prop="status" |
| | | label="发布状态" |
| | | align="center" |
| | | width="120"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getStatusType(scope.row.status)"> |
| | | {{ getStatusText(scope.row.status) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" align="center" width="200" fixed="right"> |
| | | <el-table-column label="操作" |
| | | align="center" |
| | | width="200" |
| | | fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button type="primary" link @click="viewDetail(scope.row)">查看</el-button> |
| | | <el-button |
| | | v-if="scope.row.status == '0'" |
| | | type="primary" |
| | | link |
| | | @click="handleApproval(scope.row)" |
| | | > |
| | | <el-button type="primary" |
| | | link |
| | | @click="viewDetail(scope.row)">查看</el-button> |
| | | <el-button v-if="scope.row.status == '0'" |
| | | type="primary" |
| | | link |
| | | @click="handleApproval(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" |
| | | /> |
| | | <pagination v-show="total > 0" |
| | | :total="total" |
| | | v-model:page="queryParams.current" |
| | | v-model:limit="queryParams.size" |
| | | @pagination="getList" /> |
| | | </el-card> |
| | | |
| | | <!-- 会议详情对话框 --> |
| | | <el-dialog |
| | | title="会议详情" |
| | | v-model="detailDialogVisible" |
| | | width="800px" |
| | | > |
| | | <el-dialog title="会议详情" |
| | | v-model="detailDialogVisible" |
| | | width="800px"> |
| | | <div v-if="currentMeeting"> |
| | | <el-descriptions label-width="100px" class="meeting-desc" :column="2" border> |
| | | <el-descriptions-item label="会议主题" label-class-name="nowrap-label">{{ |
| | | <el-descriptions label-width="100px" |
| | | class="meeting-desc" |
| | | :column="2" |
| | | border> |
| | | <el-descriptions-item label="会议主题" |
| | | label-class-name="nowrap-label">{{ |
| | | currentMeeting.title |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="申请人" label-class-name="nowrap-label">{{ |
| | | <el-descriptions-item label="申请人" |
| | | label-class-name="nowrap-label">{{ |
| | | currentMeeting.applicant |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="主理人" label-class-name="nowrap-label">{{ |
| | | <el-descriptions-item label="主理人" |
| | | label-class-name="nowrap-label">{{ |
| | | currentMeeting.host |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="会议时间" :span="2" label-class-name="nowrap-label"> |
| | | <el-descriptions-item label="会议时间" |
| | | :span="2" |
| | | label-class-name="nowrap-label"> |
| | | {{ formatDateTime(currentMeeting.meetingTime) }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="会议地点" label-class-name="nowrap-label">{{ |
| | | <el-descriptions-item label="会议地点" |
| | | label-class-name="nowrap-label">{{ |
| | | currentMeeting.location |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="参会人数" label-class-name="nowrap-label">{{ |
| | | <el-descriptions-item label="参会人数" |
| | | label-class-name="nowrap-label">{{ |
| | | currentMeeting.participants.length |
| | | }}人</el-descriptions-item> |
| | | <el-descriptions-item label="发布状态" label-class-name="nowrap-label"> |
| | | <el-descriptions-item label="发布状态" |
| | | label-class-name="nowrap-label"> |
| | | <el-tag :type="getStatusType(currentMeeting.status)"> |
| | | {{ getStatusText(currentMeeting.status) }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="申请时间" label-class-name="nowrap-label">{{ |
| | | <el-descriptions-item label="申请时间" |
| | | label-class-name="nowrap-label">{{ |
| | | currentMeeting.createTime |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item style="max-height: 400px" label="会议说明" :span="2" |
| | | <el-descriptions-item style="max-height: 400px" |
| | | label="会议说明" |
| | | :span="2" |
| | | label-class-name="nowrap-label">{{ currentMeeting.description }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | |
| | | <div class="content-section mt-20"> |
| | | <h4>参会人员</h4> |
| | | <div class="participants-list"> |
| | | <el-tag |
| | | v-for="participant in currentMeeting.participants" |
| | | :key="participant.id" |
| | | style="margin-right: 10px; margin-bottom: 10px;" |
| | | > |
| | | <el-tag v-for="participant in currentMeeting.participants" |
| | | :key="participant.id" |
| | | style="margin-right: 10px; margin-bottom: 10px;"> |
| | | {{ participant.name }} |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="detailDialogVisible = false">关 闭</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 会议发布对话框 --> |
| | | <el-dialog |
| | | title="会议发布" |
| | | v-model="approvalDialogVisible" |
| | | > |
| | | <el-dialog title="会议发布" |
| | | v-model="approvalDialogVisible"> |
| | | <div v-if="currentMeeting"> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions :column="2" |
| | | border> |
| | | <el-descriptions-item label="会议主题">{{ currentMeeting.title }}</el-descriptions-item> |
| | | <el-descriptions-item label="申请人">{{ currentMeeting.applicant }}</el-descriptions-item> |
| | | <el-descriptions-item label="主理人">{{ currentMeeting.host }}</el-descriptions-item> |
| | | <el-descriptions-item label="会议时间" :span="2"> |
| | | <el-descriptions-item label="会议时间" |
| | | :span="2"> |
| | | {{ formatDateTime(currentMeeting.meetingTime) }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="会议地点">{{ currentMeeting.location }}</el-descriptions-item> |
| | | <el-descriptions-item label="参会人数">{{ currentMeeting.participants.length }}人</el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div class="content-section mt-20"> |
| | | <h4>参会人员</h4> |
| | | <div class="participants-list"> |
| | | <el-tag |
| | | v-for="participant in currentMeeting.participants" |
| | | :key="participant.id" |
| | | style="margin-right: 10px; margin-bottom: 10px;" |
| | | > |
| | | <el-tag v-for="participant in currentMeeting.participants" |
| | | :key="participant.id" |
| | | style="margin-right: 10px; margin-bottom: 10px;"> |
| | | {{ participant.name }} |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="approval-opinion mt-20"> |
| | | <h4>发布意见</h4> |
| | | <el-input |
| | | v-model="publishComment" |
| | | type="textarea" |
| | | placeholder="请输入发布意见" |
| | | :rows="4" |
| | | /> |
| | | <el-input v-model="publishComment" |
| | | type="textarea" |
| | | placeholder="请输入发布意见" |
| | | :rows="4" /> |
| | | </div> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="submitApproval('1')">发 布</el-button> |
| | | <el-button @click="approvalDialogVisible = false">取 消</el-button> |
| | | <!-- <el-button type="danger" @click="submitApproval('2')">不通过</el-button>--> |
| | | <el-button type="primary" @click="submitApproval('1')">发 布</el-button> |
| | | <!-- <el-button type="danger" @click="submitApproval('2')">不通过</el-button>--> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {ref, reactive, onMounted} from 'vue' |
| | | import {ElMessage, ElMessageBox} from 'element-plus' |
| | | import Pagination from '@/components/Pagination/index.vue' |
| | | import {getRoomEnum, getMeetingPublish,saveMeetingApplication} from '@/api/collaborativeApproval/meeting.js' |
| | | import {getStaffOnJob} from "@/api/personnelManagement/onboarding.js"; |
| | | import dayjs from "dayjs"; |
| | | import { ref, reactive, onMounted } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import Pagination from "@/components/Pagination/index.vue"; |
| | | import { |
| | | getRoomEnum, |
| | | getMeetingPublish, |
| | | saveMeetingApplication, |
| | | } from "@/api/collaborativeApproval/meeting.js"; |
| | | import { getStaffOnJob } from "@/api/personnelManagement/onboarding.js"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | // 数据列表加载状态 |
| | | const loading = ref(false) |
| | | // 数据列表加载状态 |
| | | const loading = ref(false); |
| | | |
| | | // 总条数 |
| | | const total = ref(0) |
| | | const roomEnum = ref([]) |
| | | const staffList = ref([]) |
| | | // 发布列表数据 |
| | | const approvalList = ref([]) |
| | | // 总条数 |
| | | const total = ref(0); |
| | | const roomEnum = ref([]); |
| | | const staffList = ref([]); |
| | | // 发布列表数据 |
| | | const approvalList = ref([]); |
| | | |
| | | // 查询参数 |
| | | const queryParams = reactive({ |
| | | current: 1, |
| | | size: 10 |
| | | }) |
| | | // 查询参数 |
| | | const queryParams = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | }); |
| | | |
| | | // 搜索表单 |
| | | const searchForm = reactive({ |
| | | title: '', |
| | | applicant: '', |
| | | status: '' |
| | | }) |
| | | // 搜索表单 |
| | | const searchForm = reactive({ |
| | | title: "", |
| | | applicant: "", |
| | | status: "", |
| | | }); |
| | | |
| | | // 是否显示对话框 |
| | | const detailDialogVisible = ref(false) |
| | | const approvalDialogVisible = ref(false) |
| | | // 是否显示对话框 |
| | | const detailDialogVisible = ref(false); |
| | | const approvalDialogVisible = ref(false); |
| | | |
| | | // 当前查看的会议 |
| | | const currentMeeting = ref(null) |
| | | // 当前查看的会议 |
| | | const currentMeeting = ref(null); |
| | | |
| | | // 发布意见 |
| | | const publishComment = ref('') |
| | | // 发布意见 |
| | | const publishComment = ref(""); |
| | | |
| | | // 查询数据 |
| | | const getList = async () => { |
| | | loading.value = true |
| | | let resp = await getMeetingPublish({...searchForm, ...queryParams}) |
| | | approvalList.value = resp.data.records.map(it => { |
| | | let room = roomEnum.value.find(room => it.roomId === room.id) |
| | | it.location = `${room.name}(${room.location})` |
| | | let staffs = JSON.parse(it.participants) |
| | | it.staffCount = staffs.size |
| | | it.status = it.publishStatus |
| | | it.meetingTime = `${it.meetingDate} ${dayjs(it.startTime).format('HH:mm:ss')} ~ ${dayjs(it.endTime).format('HH:mm:ss')}` |
| | | it.participants = staffList.value.filter(staff => staffs.some(id=>id === staff.id)).map(staff => { |
| | | return { |
| | | id: staff.id, |
| | | name: `${staff.staffName}(${staff.postJob})` |
| | | } |
| | | }) |
| | | // 查询数据 |
| | | const getList = async () => { |
| | | loading.value = true; |
| | | let resp = await getMeetingPublish({ ...searchForm, ...queryParams }); |
| | | approvalList.value = resp.data.records.map(it => { |
| | | let room = roomEnum.value.find(room => it.roomId === room.id); |
| | | it.location = `${room.name}(${room.location})`; |
| | | let staffs = JSON.parse(it.participants); |
| | | it.staffCount = staffs.size; |
| | | it.status = it.publishStatus; |
| | | it.meetingTime = `${it.meetingDate} ${dayjs(it.startTime).format( |
| | | "HH:mm:ss" |
| | | )} ~ ${dayjs(it.endTime).format("HH:mm:ss")}`; |
| | | it.participants = staffList.value |
| | | .filter(staff => staffs.some(id => id === staff.id)) |
| | | .map(staff => { |
| | | return { |
| | | id: staff.id, |
| | | name: `${staff.staffName}(${staff.postJob})`, |
| | | }; |
| | | }); |
| | | |
| | | return it; |
| | | }); |
| | | total.value = resp.data.total; |
| | | loading.value = false; |
| | | }; |
| | | |
| | | return it |
| | | }) |
| | | total.value = resp.data.total |
| | | loading.value = false |
| | | } |
| | | // 搜索按钮操作 |
| | | const handleSearch = () => { |
| | | queryParams.pageNum = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | // 搜索按钮操作 |
| | | const handleSearch = () => { |
| | | queryParams.pageNum = 1 |
| | | getList() |
| | | } |
| | | // 重置搜索表单 |
| | | const resetSearch = () => { |
| | | Object.assign(searchForm, { |
| | | title: "", |
| | | applicant: "", |
| | | status: "", |
| | | }); |
| | | handleSearch(); |
| | | }; |
| | | |
| | | // 重置搜索表单 |
| | | const resetSearch = () => { |
| | | Object.assign(searchForm, { |
| | | title: '', |
| | | applicant: '', |
| | | status: '' |
| | | }) |
| | | handleSearch() |
| | | } |
| | | // 查看详情 |
| | | const viewDetail = row => { |
| | | currentMeeting.value = row; |
| | | detailDialogVisible.value = true; |
| | | }; |
| | | |
| | | // 查看详情 |
| | | const viewDetail = (row) => { |
| | | currentMeeting.value = row |
| | | detailDialogVisible.value = true |
| | | } |
| | | // 处理发布 |
| | | const handleApproval = row => { |
| | | currentMeeting.value = row; |
| | | publishComment.value = ""; |
| | | approvalDialogVisible.value = true; |
| | | }; |
| | | |
| | | // 处理发布 |
| | | const handleApproval = (row) => { |
| | | currentMeeting.value = row |
| | | publishComment.value = '' |
| | | approvalDialogVisible.value = true |
| | | } |
| | | // 获取状态类型 |
| | | const getStatusType = status => { |
| | | const statusMap = { |
| | | 0: "info", // 待发布 |
| | | 1: "success", // 已通过 |
| | | 2: "danger", // 未通过 |
| | | }; |
| | | return statusMap[status] || "info"; |
| | | }; |
| | | |
| | | // 获取状态类型 |
| | | const getStatusType = (status) => { |
| | | const statusMap = { |
| | | '0': 'info', // 待发布 |
| | | '1': 'success', // 已通过 |
| | | '2': 'danger', // 未通过 |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | // 获取状态文本 |
| | | const getStatusText = status => { |
| | | const statusMap = { |
| | | 0: "待发布", |
| | | 1: "已发布", |
| | | 2: "已取消", |
| | | }; |
| | | return statusMap[status] || "未知"; |
| | | }; |
| | | |
| | | // 获取状态文本 |
| | | const getStatusText = (status) => { |
| | | const statusMap = { |
| | | '0': '待发布', |
| | | '1': '已发布', |
| | | '2': '已取消', |
| | | } |
| | | return statusMap[status] || '未知' |
| | | } |
| | | // 格式化日期时间 |
| | | const formatDateTime = dateTime => { |
| | | if (!dateTime) return ""; |
| | | return dateTime.replace(" ", "\n"); |
| | | }; |
| | | |
| | | // 格式化日期时间 |
| | | const formatDateTime = (dateTime) => { |
| | | if (!dateTime) return '' |
| | | return dateTime.replace(' ', '\n') |
| | | } |
| | | // 提交发布 |
| | | const submitApproval = status => { |
| | | // if (status === 'approved' && !publishComment.value.trim()) { |
| | | // ElMessage.warning('请填写发布意见') |
| | | // return |
| | | // } |
| | | |
| | | // 提交发布 |
| | | const submitApproval = (status) => { |
| | | // if (status === 'approved' && !publishComment.value.trim()) { |
| | | // ElMessage.warning('请填写发布意见') |
| | | // return |
| | | // } |
| | | |
| | | ElMessageBox.confirm( |
| | | `确认${status === '1' ? '发布' : '取消'}该会议?`, |
| | | '发布确认', |
| | | ElMessageBox.confirm( |
| | | `确认${status === "1" ? "发布" : "取消"}该会议?`, |
| | | "发布确认", |
| | | { |
| | | confirmButtonText: '确定', |
| | | cancelButtonText: '取消', |
| | | type: 'warning' |
| | | confirmButtonText: "确定", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | } |
| | | ).then(() => { |
| | | saveMeetingApplication({ |
| | | id: currentMeeting.value.id, |
| | | publishStatus: status, |
| | | publishComment: publishComment.value |
| | | }).then(resp=>{ |
| | | // 更新会议状态 |
| | | currentMeeting.value.status = status |
| | | ) |
| | | .then(() => { |
| | | saveMeetingApplication({ |
| | | id: currentMeeting.value.id, |
| | | publishStatus: status, |
| | | publishComment: publishComment.value, |
| | | }).then(resp => { |
| | | // 更新会议状态 |
| | | currentMeeting.value.status = status; |
| | | |
| | | ElMessage.success('发布提交成功') |
| | | approvalDialogVisible.value = false |
| | | getList() |
| | | }) |
| | | ElMessage.success("发布提交成功"); |
| | | approvalDialogVisible.value = false; |
| | | getList(); |
| | | }); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | }).catch(() => { |
| | | }) |
| | | } |
| | | // 页面加载时获取数据 |
| | | onMounted(async () => { |
| | | const [resp1, resp2] = await Promise.all([getRoomEnum(), getStaffOnJob()]); |
| | | roomEnum.value = resp1.data; |
| | | staffList.value = resp2.data; |
| | | |
| | | // 页面加载时获取数据 |
| | | onMounted(async () => { |
| | | const [resp1, resp2]= await Promise.all([getRoomEnum(), getStaffOnJob()]) |
| | | roomEnum.value = resp1.data |
| | | staffList.value = resp2.data |
| | | |
| | | await getList() |
| | | }) |
| | | await getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .page-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | } |
| | | .page-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | margin: 0; |
| | | color: #303133; |
| | | } |
| | | .page-header h2 { |
| | | margin: 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .search-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | .search-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .content-section h4 { |
| | | margin: 0 0 15px 0; |
| | | color: #303133; |
| | | } |
| | | .content-section h4 { |
| | | margin: 0 0 15px 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .mt-20 { |
| | | margin-top: 20px; |
| | | } |
| | | .mt-20 { |
| | | margin-top: 20px; |
| | | } |
| | | |
| | | .participants-list { |
| | | min-height: 40px; |
| | | padding: 15px; |
| | | border-radius: 4px; |
| | | line-height: 1.6; |
| | | } |
| | | .participants-list { |
| | | min-height: 40px; |
| | | padding: 15px; |
| | | border-radius: 4px; |
| | | line-height: 1.6; |
| | | } |
| | | |
| | | .approval-opinion h4 { |
| | | margin: 0 0 15px 0; |
| | | color: #303133; |
| | | } |
| | | .approval-opinion h4 { |
| | | margin: 0 0 15px 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .nowrap-label { |
| | | white-space: nowrap !important; |
| | | } |
| | | .nowrap-label { |
| | | white-space: nowrap !important; |
| | | } |
| | | |
| | | .description-content { |
| | | white-space: pre-wrap; |
| | | word-wrap: break-word; |
| | | line-height: 1.6; |
| | | padding: 10px; |
| | | background-color: #f5f7fa; |
| | | border-radius: 4px; |
| | | min-height: 60px; |
| | | } |
| | | .description-content { |
| | | white-space: pre-wrap; |
| | | word-wrap: break-word; |
| | | line-height: 1.6; |
| | | padding: 10px; |
| | | background-color: #f5f7fa; |
| | | border-radius: 4px; |
| | | min-height: 60px; |
| | | } |
| | | </style> |
| | |
| | | <div class="page-header"> |
| | | <h2>会议纪要</h2> |
| | | </div> |
| | | |
| | | <!-- 搜索区域 --> |
| | | <el-card class="search-card"> |
| | | <el-form :model="searchForm" inline> |
| | | <el-form :model="searchForm" |
| | | inline> |
| | | <el-form-item label="会议主题"> |
| | | <el-input v-model="searchForm.title" placeholder="请输入会议主题" clearable /> |
| | | <el-input v-model="searchForm.title" |
| | | placeholder="请输入会议主题" |
| | | clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="申请人"> |
| | | <el-input v-model="searchForm.applicant" placeholder="请输入申请人" clearable /> |
| | | <el-input v-model="searchForm.applicant" |
| | | placeholder="请输入申请人" |
| | | clearable /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch">搜索</el-button> |
| | | <el-button type="primary" |
| | | @click="handleSearch">搜索</el-button> |
| | | <el-button @click="resetSearch">重置</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | | |
| | | <!-- 会议列表 --> |
| | | <el-card> |
| | | <el-table v-loading="loading" :data="meetingList" border> |
| | | <el-table-column prop="title" label="会议主题" align="center" min-width="200" show-overflow-tooltip /> |
| | | <el-table-column prop="applicant" label="申请人" align="center" width="120" /> |
| | | <el-table-column prop="host" label="主持人" align="center" width="120" /> |
| | | <el-table-column prop="meetingTime" label="会议时间" align="center" width="180"> |
| | | <el-table v-loading="loading" |
| | | :data="meetingList" |
| | | border> |
| | | <el-table-column prop="title" |
| | | label="会议主题" |
| | | align="center" |
| | | min-width="200" |
| | | show-overflow-tooltip /> |
| | | <el-table-column prop="applicant" |
| | | label="申请人" |
| | | align="center" |
| | | width="120" /> |
| | | <el-table-column prop="host" |
| | | label="主持人" |
| | | align="center" |
| | | width="120" /> |
| | | <el-table-column prop="meetingTime" |
| | | label="会议时间" |
| | | align="center" |
| | | width="180"> |
| | | <template #default="scope"> |
| | | {{ formatDateTime(scope.row.meetingTime) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="location" label="会议地点" align="center" width="150" /> |
| | | <el-table-column prop="participants" label="参会人数" align="center" width="100"> |
| | | <el-table-column prop="location" |
| | | label="会议地点" |
| | | align="center" |
| | | width="150" /> |
| | | <el-table-column prop="participants" |
| | | label="参会人数" |
| | | align="center" |
| | | width="100"> |
| | | <template #default="scope"> |
| | | {{ scope.row.participants.length }}人 |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" align="center" width="200" fixed="right"> |
| | | <el-table-column label="操作" |
| | | align="center" |
| | | width="200" |
| | | fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button type="primary" link @click="viewDetail(scope.row)">查看</el-button> |
| | | <el-button |
| | | type="primary" |
| | | link |
| | | @click="addMinutes(scope.row)" |
| | | > |
| | | <el-button type="primary" |
| | | link |
| | | @click="viewDetail(scope.row)">查看</el-button> |
| | | <el-button type="primary" |
| | | link |
| | | @click="addMinutes(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" |
| | | /> |
| | | <pagination v-show="total > 0" |
| | | :total="total" |
| | | v-model:page="queryParams.current" |
| | | v-model:limit="queryParams.size" |
| | | @pagination="getList" /> |
| | | </el-card> |
| | | |
| | | <!-- 会议详情对话框 --> |
| | | <el-dialog |
| | | title="会议详情" |
| | | v-model="detailDialogVisible" |
| | | width="800px" |
| | | > |
| | | <el-dialog title="会议详情" |
| | | v-model="detailDialogVisible" |
| | | width="800px"> |
| | | <div v-if="currentMeeting"> |
| | | <el-descriptions label-width="100px" class="meeting-desc" :column="2" border> |
| | | <el-descriptions-item label="会议主题" label-class-name="nowrap-label">{{ |
| | | <el-descriptions label-width="100px" |
| | | class="meeting-desc" |
| | | :column="2" |
| | | border> |
| | | <el-descriptions-item label="会议主题" |
| | | label-class-name="nowrap-label">{{ |
| | | currentMeeting.title |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="申请人" label-class-name="nowrap-label">{{ |
| | | <el-descriptions-item label="申请人" |
| | | label-class-name="nowrap-label">{{ |
| | | currentMeeting.applicant |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="主持人" label-class-name="nowrap-label">{{ |
| | | <el-descriptions-item label="主持人" |
| | | label-class-name="nowrap-label">{{ |
| | | currentMeeting.host |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="会议时间" :span="2" label-class-name="nowrap-label"> |
| | | <el-descriptions-item label="会议时间" |
| | | :span="2" |
| | | label-class-name="nowrap-label"> |
| | | {{ formatDateTime(currentMeeting.meetingTime) }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="会议地点" label-class-name="nowrap-label">{{ |
| | | <el-descriptions-item label="会议地点" |
| | | label-class-name="nowrap-label">{{ |
| | | currentMeeting.location |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="参会人数" label-class-name="nowrap-label">{{ |
| | | <el-descriptions-item label="参会人数" |
| | | label-class-name="nowrap-label">{{ |
| | | currentMeeting.participants.length |
| | | }}人</el-descriptions-item> |
| | | <el-descriptions-item label="审批状态" label-class-name="nowrap-label"> |
| | | <el-descriptions-item label="审批状态" |
| | | label-class-name="nowrap-label"> |
| | | <el-tag :type="getStatusType(currentMeeting.status)"> |
| | | {{ getStatusText(currentMeeting.status) }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="申请时间" label-class-name="nowrap-label">{{ |
| | | <el-descriptions-item label="申请时间" |
| | | label-class-name="nowrap-label">{{ |
| | | currentMeeting.createTime |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item style="max-height: 400px" label="会议说明" :span="2" |
| | | label-class-name="nowrap-label">{{ currentMeeting.description }}</el-descriptions-item> |
| | | <el-descriptions-item style="max-height: 400px" |
| | | label="会议说明" |
| | | :span="2" |
| | | label-class-name="nowrap-label">{{ currentMeeting.description }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div class="content-section mt-20"> |
| | | <h4>参会人员</h4> |
| | | <div class="participants-list"> |
| | | <el-tag |
| | | v-for="participant in currentMeeting.participants" |
| | | :key="participant.id" |
| | | style="margin-right: 10px; margin-bottom: 10px;" |
| | | > |
| | | <el-tag v-for="participant in currentMeeting.participants" |
| | | :key="participant.id" |
| | | style="margin-right: 10px; margin-bottom: 10px;"> |
| | | {{ participant.name }} |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="detailDialogVisible = false">关 闭</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 添加会议纪要对话框 --> |
| | | <el-dialog |
| | | title="添加会议纪要" |
| | | v-model="minutesDialogVisible" |
| | | width="80%" |
| | | @close="handleCloseMinutesDialog" |
| | | > |
| | | <el-dialog title="添加会议纪要" |
| | | v-model="minutesDialogVisible" |
| | | width="80%" |
| | | @close="handleCloseMinutesDialog"> |
| | | <div v-if="currentMeeting"> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions :column="2" |
| | | border> |
| | | <el-descriptions-item label="会议主题">{{ currentMeeting.title }}</el-descriptions-item> |
| | | <el-descriptions-item label="申请人">{{ currentMeeting.applicant }}</el-descriptions-item> |
| | | <el-descriptions-item label="主持人">{{ currentMeeting.host }}</el-descriptions-item> |
| | | <el-descriptions-item label="会议时间" :span="2"> |
| | | <el-descriptions-item label="会议时间" |
| | | :span="2"> |
| | | {{ formatDateTime(currentMeeting.meetingTime) }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="会议地点">{{ currentMeeting.location }}</el-descriptions-item> |
| | | <el-descriptions-item label="参会人数">{{ currentMeeting.participants.length }}人</el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div class="content-section mt-20"> |
| | | <h4>会议纪要内容</h4> |
| | | <div class="editor-container"> |
| | | <Editor |
| | | v-model="minutesContent" |
| | | :min-height="400" |
| | | /> |
| | | <Editor v-model="minutesContent" |
| | | :min-height="400" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="submitMinutes">保 存</el-button> |
| | | <el-button @click="minutesDialogVisible = false">取 消</el-button> |
| | | <el-button type="primary" @click="submitMinutes">保 存</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import Pagination from '@/components/Pagination/index.vue' |
| | | import Editor from '@/components/Editor/index.vue' |
| | | import { getRoomEnum, getMeetingPublish ,getMeetingMinutesByMeetingId,saveMeetingMinutes} from '@/api/collaborativeApproval/meeting.js' |
| | | import { getStaffOnJob } from "@/api/personnelManagement/onboarding.js" |
| | | import dayjs from "dayjs" |
| | | import { ref, reactive, onMounted } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import Pagination from "@/components/Pagination/index.vue"; |
| | | import Editor from "@/components/Editor/index.vue"; |
| | | import { |
| | | getRoomEnum, |
| | | getMeetingPublish, |
| | | getMeetingMinutesByMeetingId, |
| | | saveMeetingMinutes, |
| | | } from "@/api/collaborativeApproval/meeting.js"; |
| | | import { getStaffOnJob } from "@/api/personnelManagement/onboarding.js"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | // 数据列表加载状态 |
| | | const loading = ref(false) |
| | | // 数据列表加载状态 |
| | | const loading = ref(false); |
| | | |
| | | // 总条数 |
| | | const total = ref(0) |
| | | const roomEnum = ref([]) |
| | | const staffList = ref([]) |
| | | // 总条数 |
| | | const total = ref(0); |
| | | const roomEnum = ref([]); |
| | | const staffList = ref([]); |
| | | |
| | | // 会议列表数据 |
| | | const meetingList = ref([]) |
| | | // 会议列表数据 |
| | | const meetingList = ref([]); |
| | | |
| | | // 查询参数 |
| | | const queryParams = reactive({ |
| | | current: 1, |
| | | size: 10 |
| | | }) |
| | | // 查询参数 |
| | | const queryParams = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | }); |
| | | |
| | | // 搜索表单 |
| | | const searchForm = reactive({ |
| | | title: '', |
| | | applicant: '', |
| | | // status: '1' // 默认只显示已通过审批的会议 |
| | | }) |
| | | // 搜索表单 |
| | | const searchForm = reactive({ |
| | | title: "", |
| | | applicant: "", |
| | | // status: '1' // 默认只显示已通过审批的会议 |
| | | }); |
| | | |
| | | // 是否显示对话框 |
| | | const detailDialogVisible = ref(false) |
| | | const minutesDialogVisible = ref(false) |
| | | // 是否显示对话框 |
| | | const detailDialogVisible = ref(false); |
| | | const minutesDialogVisible = ref(false); |
| | | |
| | | // 当前查看的会议 |
| | | const currentMeeting = ref(null) |
| | | // 当前查看的会议 |
| | | const currentMeeting = ref(null); |
| | | |
| | | // 会议纪要内容 |
| | | const minutesContent = ref('') |
| | | const minutesContentId = ref('') |
| | | // 会议纪要内容 |
| | | const minutesContent = ref(""); |
| | | const minutesContentId = ref(""); |
| | | |
| | | // 查询数据 |
| | | const getList = async () => { |
| | | loading.value = true |
| | | let resp = await getMeetingPublish({ ...searchForm, ...queryParams }) |
| | | meetingList.value = resp.data.records.map(it => { |
| | | let room = roomEnum.value.find(room => it.roomId === room.id) |
| | | it.location = `${room.name}(${room.location})` |
| | | let staffs = JSON.parse(it.participants) |
| | | it.staffCount = staffs.size |
| | | it.meetingTime = `${it.meetingDate} ${dayjs(it.startTime).format('HH:mm:ss')} ~ ${dayjs(it.endTime).format('HH:mm:ss')}` |
| | | it.participants = staffList.value.filter(staff => staffs.some(id => id === staff.id)).map(staff => { |
| | | return { |
| | | id: staff.id, |
| | | name: `${staff.staffName}(${staff.postJob})` |
| | | } |
| | | }) |
| | | // 查询数据 |
| | | const getList = async () => { |
| | | loading.value = true; |
| | | let resp = await getMeetingPublish({ ...searchForm, ...queryParams }); |
| | | meetingList.value = resp.data.records.map(it => { |
| | | let room = roomEnum.value.find(room => it.roomId === room.id); |
| | | it.location = `${room.name}(${room.location})`; |
| | | let staffs = JSON.parse(it.participants); |
| | | it.staffCount = staffs.size; |
| | | it.meetingTime = `${it.meetingDate} ${dayjs(it.startTime).format( |
| | | "HH:mm:ss" |
| | | )} ~ ${dayjs(it.endTime).format("HH:mm:ss")}`; |
| | | it.participants = staffList.value |
| | | .filter(staff => staffs.some(id => id === staff.id)) |
| | | .map(staff => { |
| | | return { |
| | | id: staff.id, |
| | | name: `${staff.staffName}(${staff.postJob})`, |
| | | }; |
| | | }); |
| | | |
| | | return it |
| | | }) |
| | | total.value = resp.data.total |
| | | loading.value = false |
| | | } |
| | | return it; |
| | | }); |
| | | total.value = resp.data.total; |
| | | loading.value = false; |
| | | }; |
| | | |
| | | // 搜索按钮操作 |
| | | const handleSearch = () => { |
| | | queryParams.current = 1 |
| | | getList() |
| | | } |
| | | // 搜索按钮操作 |
| | | const handleSearch = () => { |
| | | queryParams.current = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | // 重置搜索表单 |
| | | const resetSearch = () => { |
| | | Object.assign(searchForm, { |
| | | title: '', |
| | | applicant: '', |
| | | // status: '1' |
| | | }) |
| | | handleSearch() |
| | | } |
| | | // 重置搜索表单 |
| | | const resetSearch = () => { |
| | | Object.assign(searchForm, { |
| | | title: "", |
| | | applicant: "", |
| | | // status: '1' |
| | | }); |
| | | handleSearch(); |
| | | }; |
| | | |
| | | // 查看详情 |
| | | const viewDetail = (row) => { |
| | | currentMeeting.value = row |
| | | detailDialogVisible.value = true |
| | | } |
| | | // 查看详情 |
| | | const viewDetail = row => { |
| | | currentMeeting.value = row; |
| | | detailDialogVisible.value = true; |
| | | }; |
| | | |
| | | // 添加会议纪要 |
| | | const addMinutes = async (row) => { |
| | | let resp = await getMeetingMinutesByMeetingId(row.id) |
| | | currentMeeting.value = row |
| | | if (resp.data){ |
| | | minutesContent.value = resp.data.content |
| | | minutesContentId.value = resp.data.id |
| | | }else { |
| | | minutesContent.value = `<h2>${row.title}会议纪要</h2> |
| | | <p><strong>会议时间:</strong>${row.meetingTime}</p> |
| | | <p><strong>会议地点:</strong>${row.location}</p> |
| | | <p><strong>主持人:</strong>${row.host}</p> |
| | | <p><strong>参会人员:</strong></p> |
| | | <ol> |
| | | ${row.participants.map(p => `<li>${p.name}</li>`).join('')} |
| | | </ol> |
| | | <p><strong>会议内容:</strong></p> |
| | | <ol> |
| | | <li>议题一: |
| | | <ul> |
| | | <li>讨论内容:</li> |
| | | <li>决议事项:</li> |
| | | </ul> |
| | | </li> |
| | | <li>议题二: |
| | | <ul> |
| | | <li>讨论内容:</li> |
| | | <li>决议事项:</li> |
| | | </ul> |
| | | </li> |
| | | </ol> |
| | | <p><strong>备注:</strong></p>` |
| | | } |
| | | // 添加会议纪要 |
| | | const addMinutes = async row => { |
| | | let resp = await getMeetingMinutesByMeetingId(row.id); |
| | | currentMeeting.value = row; |
| | | if (resp.data) { |
| | | minutesContent.value = resp.data.content; |
| | | minutesContentId.value = resp.data.id; |
| | | } else { |
| | | minutesContent.value = `<h2>${row.title}会议纪要</h2> |
| | | <p><strong>会议时间:</strong>${row.meetingTime}</p> |
| | | <p><strong>会议地点:</strong>${row.location}</p> |
| | | <p><strong>主持人:</strong>${row.host}</p> |
| | | <p><strong>参会人员:</strong></p> |
| | | <ol> |
| | | ${row.participants.map(p => `<li>${p.name}</li>`).join("")} |
| | | </ol> |
| | | <p><strong>会议内容:</strong></p> |
| | | <ol> |
| | | <li>议题一: |
| | | <ul> |
| | | <li>讨论内容:</li> |
| | | <li>决议事项:</li> |
| | | </ul> |
| | | </li> |
| | | <li>议题二: |
| | | <ul> |
| | | <li>讨论内容:</li> |
| | | <li>决议事项:</li> |
| | | </ul> |
| | | </li> |
| | | </ol> |
| | | <p><strong>备注:</strong></p>`; |
| | | } |
| | | |
| | | minutesDialogVisible.value = true |
| | | } |
| | | minutesDialogVisible.value = true; |
| | | }; |
| | | |
| | | // 提交会议纪要 |
| | | const submitMinutes = () => { |
| | | if (!minutesContent.value) { |
| | | ElMessage.warning('请输入会议纪要内容') |
| | | return |
| | | } |
| | | saveMeetingMinutes({ |
| | | id: minutesContentId.value, |
| | | content: minutesContent.value, |
| | | meetingId: currentMeeting.value.id, |
| | | title: currentMeeting.value.title |
| | | }).then(resp=>{ |
| | | console.log('会议纪要内容:', minutesContent.value) |
| | | ElMessage.success('会议纪要保存成功') |
| | | minutesDialogVisible.value = false |
| | | }) |
| | | // 提交会议纪要 |
| | | const submitMinutes = () => { |
| | | if (!minutesContent.value) { |
| | | ElMessage.warning("请输入会议纪要内容"); |
| | | return; |
| | | } |
| | | saveMeetingMinutes({ |
| | | id: minutesContentId.value, |
| | | content: minutesContent.value, |
| | | meetingId: currentMeeting.value.id, |
| | | title: currentMeeting.value.title, |
| | | }).then(resp => { |
| | | console.log("会议纪要内容:", minutesContent.value); |
| | | ElMessage.success("会议纪要保存成功"); |
| | | minutesDialogVisible.value = false; |
| | | }); |
| | | }; |
| | | |
| | | } |
| | | // 关闭会议纪要对话框 |
| | | const handleCloseMinutesDialog = () => { |
| | | minutesContent.value = ""; |
| | | }; |
| | | |
| | | // 关闭会议纪要对话框 |
| | | const handleCloseMinutesDialog = () => { |
| | | minutesContent.value = '' |
| | | } |
| | | // 获取状态类型 |
| | | const getStatusType = status => { |
| | | const statusMap = { |
| | | 0: "info", // 待审批 |
| | | 1: "success", // 已通过 |
| | | 2: "warning", // 未通过 |
| | | 3: "danger", // 取消 |
| | | }; |
| | | return statusMap[status] || "info"; |
| | | }; |
| | | |
| | | // 获取状态类型 |
| | | const getStatusType = (status) => { |
| | | const statusMap = { |
| | | '0': 'info', // 待审批 |
| | | '1': 'success', // 已通过 |
| | | '2': 'warning', // 未通过 |
| | | '3': 'danger' // 取消 |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | // 获取状态文本 |
| | | const getStatusText = status => { |
| | | const statusMap = { |
| | | 0: "待审批", |
| | | 1: "已通过", |
| | | 2: "未通过", |
| | | 3: "已取消", |
| | | }; |
| | | return statusMap[status] || "未知"; |
| | | }; |
| | | |
| | | // 获取状态文本 |
| | | const getStatusText = (status) => { |
| | | const statusMap = { |
| | | '0': '待审批', |
| | | '1': '已通过', |
| | | '2': '未通过', |
| | | '3': '已取消' |
| | | } |
| | | return statusMap[status] || '未知' |
| | | } |
| | | // 格式化日期时间 |
| | | const formatDateTime = dateTime => { |
| | | if (!dateTime) return ""; |
| | | return dateTime.replace(" ", "\n"); |
| | | }; |
| | | |
| | | // 格式化日期时间 |
| | | const formatDateTime = (dateTime) => { |
| | | if (!dateTime) return '' |
| | | return dateTime.replace(' ', '\n') |
| | | } |
| | | // 页面加载时获取数据 |
| | | onMounted(async () => { |
| | | const [resp1, resp2] = await Promise.all([getRoomEnum(), getStaffOnJob()]); |
| | | roomEnum.value = resp1.data; |
| | | staffList.value = resp2.data; |
| | | |
| | | // 页面加载时获取数据 |
| | | onMounted(async () => { |
| | | const [resp1, resp2] = await Promise.all([getRoomEnum(), getStaffOnJob()]) |
| | | roomEnum.value = resp1.data |
| | | staffList.value = resp2.data |
| | | |
| | | await getList() |
| | | }) |
| | | await getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .page-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | } |
| | | .page-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | margin: 0; |
| | | color: #303133; |
| | | } |
| | | .page-header h2 { |
| | | margin: 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .search-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | .search-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .content-section h4 { |
| | | margin: 0 0 15px 0; |
| | | color: #303133; |
| | | } |
| | | .content-section h4 { |
| | | margin: 0 0 15px 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .mt-20 { |
| | | margin-top: 20px; |
| | | } |
| | | .mt-20 { |
| | | margin-top: 20px; |
| | | } |
| | | |
| | | .participants-list { |
| | | min-height: 40px; |
| | | padding: 15px; |
| | | border-radius: 4px; |
| | | line-height: 1.6; |
| | | } |
| | | .participants-list { |
| | | min-height: 40px; |
| | | padding: 15px; |
| | | border-radius: 4px; |
| | | line-height: 1.6; |
| | | } |
| | | |
| | | .nowrap-label { |
| | | white-space: nowrap !important; |
| | | } |
| | | .nowrap-label { |
| | | white-space: nowrap !important; |
| | | } |
| | | |
| | | .editor-container { |
| | | border: 1px solid #dcdfe6; |
| | | border-radius: 4px; |
| | | } |
| | | .editor-container { |
| | | border: 1px solid #dcdfe6; |
| | | border-radius: 4px; |
| | | } |
| | | </style> |
| | |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>办公物资申请管理</span> |
| | | <el-button type="primary" @click="openShow()"> |
| | | <el-icon><Plus /></el-icon> |
| | | <el-button type="primary" |
| | | @click="openShow()"> |
| | | <el-icon> |
| | | <Plus /> |
| | | </el-icon> |
| | | 新建申请 |
| | | </el-button> |
| | | </div> |
| | | </template> |
| | | |
| | | <!-- 搜索区域 --> |
| | | <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch"> |
| | | <el-form-item label="申请编号" prop="code"> |
| | | <el-input |
| | | v-model="queryParams.code" |
| | | placeholder="请输入申请编号" |
| | | clearable |
| | | style="width: 200px" |
| | | @keyup.enter="handleQuery" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="申请人" prop="applicant"> |
| | | <el-input |
| | | v-model="queryParams.applicant" |
| | | placeholder="请输入申请人" |
| | | clearable |
| | | style="width: 200px" |
| | | @keyup.enter="handleQuery" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="申请状态" prop="status"> |
| | | <el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 200px"> |
| | | <el-option label="待审批" value="1" /> |
| | | <el-option label="已通过" value="3" /> |
| | | <el-option label="已拒绝" value="2" /> |
| | | <el-option label="已发放" value="4" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleQuery"> |
| | | <el-icon><Search /></el-icon> |
| | | 搜索 |
| | | </el-button> |
| | | <el-button @click="resetQuery"> |
| | | <el-icon><Refresh /></el-icon> |
| | | 重置 |
| | | </el-button> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleExport"> |
| | | <el-icon><Download /></el-icon> |
| | | <!-- 搜索区域 --> |
| | | <el-form :model="queryParams" |
| | | ref="queryRef" |
| | | :inline="true" |
| | | v-show="showSearch"> |
| | | <el-form-item label="申请编号" |
| | | prop="code"> |
| | | <el-input v-model="queryParams.code" |
| | | placeholder="请输入申请编号" |
| | | clearable |
| | | style="width: 200px" |
| | | @keyup.enter="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="申请人" |
| | | prop="applicant"> |
| | | <el-input v-model="queryParams.applicant" |
| | | placeholder="请输入申请人" |
| | | clearable |
| | | style="width: 200px" |
| | | @keyup.enter="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="申请状态" |
| | | prop="status"> |
| | | <el-select v-model="queryParams.status" |
| | | placeholder="请选择状态" |
| | | clearable |
| | | style="width: 200px"> |
| | | <el-option label="待审批" |
| | | value="1" /> |
| | | <el-option label="已通过" |
| | | value="3" /> |
| | | <el-option label="已拒绝" |
| | | value="2" /> |
| | | <el-option label="已发放" |
| | | value="4" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" |
| | | @click="handleQuery"> |
| | | <el-icon> |
| | | <Search /> |
| | | </el-icon> |
| | | 搜索 |
| | | </el-button> |
| | | <el-button @click="resetQuery"> |
| | | <el-icon> |
| | | <Refresh /> |
| | | </el-icon> |
| | | 重置 |
| | | </el-button> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" |
| | | @click="handleExport"> |
| | | <el-icon> |
| | | <Download /> |
| | | </el-icon> |
| | | 导出 |
| | | </el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | </el-form-item> |
| | | </el-form> |
| | | <!-- 表格区域 --> |
| | | <el-table |
| | | v-loading="loading" |
| | | :data="officeList" |
| | | @selection-change="handleSelectionChange" |
| | | style="width: 100%" |
| | | > |
| | | <el-table-column type="selection" width="55" align="center" /> |
| | | <el-table-column label="申请编号" align="center" prop="code" width="180" /> |
| | | <el-table-column label="申请人" align="center" prop="applicant" width="120" /> |
| | | <el-table-column label="部门" align="center" prop="dept" width="120" /> |
| | | <el-table-column label="物资类型" align="center" prop="materialType" width="120"> |
| | | <el-table v-loading="loading" |
| | | :data="officeList" |
| | | @selection-change="handleSelectionChange" |
| | | style="width: 100%"> |
| | | <el-table-column type="selection" |
| | | width="55" |
| | | align="center" /> |
| | | <el-table-column label="申请编号" |
| | | align="center" |
| | | prop="code" |
| | | width="180" /> |
| | | <el-table-column label="申请人" |
| | | align="center" |
| | | prop="applicant" |
| | | width="120" /> |
| | | <el-table-column label="部门" |
| | | align="center" |
| | | prop="dept" |
| | | width="120" /> |
| | | <el-table-column label="物资类型" |
| | | align="center" |
| | | prop="materialType" |
| | | width="120"> |
| | | <template #default="scope"> |
| | | <el-tag v-if="scope.row.materialType === 1" type="info">其他</el-tag> |
| | | <el-tag v-if="scope.row.materialType === 2" type="success">清洁用品</el-tag> |
| | | <el-tag v-if="scope.row.materialType === 3" type="warning">电子设备</el-tag> |
| | | <el-tag v-if="scope.row.materialType === 4" type="danger">办公用品</el-tag> |
| | | <el-tag v-if="scope.row.materialType === 1" |
| | | type="info">其他</el-tag> |
| | | <el-tag v-if="scope.row.materialType === 2" |
| | | type="success">清洁用品</el-tag> |
| | | <el-tag v-if="scope.row.materialType === 3" |
| | | type="warning">电子设备</el-tag> |
| | | <el-tag v-if="scope.row.materialType === 4" |
| | | type="danger">办公用品</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="申请数量" align="center" prop="applyNum" width="100" /> |
| | | <el-table-column label="申请原因" align="center" prop="reason" min-width="200" show-overflow-tooltip /> |
| | | <el-table-column label="申请状态" align="center" prop="status" width="100"> |
| | | <el-table-column label="申请数量" |
| | | align="center" |
| | | prop="applyNum" |
| | | width="100" /> |
| | | <el-table-column label="申请原因" |
| | | align="center" |
| | | prop="reason" |
| | | min-width="200" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="申请状态" |
| | | align="center" |
| | | prop="status" |
| | | width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getStatusType(scope.row.status)"> |
| | | {{ getStatusText(scope.row.status) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="申请时间" align="center" prop="applyTime" width="180" /> |
| | | <el-table-column label="审批人" align="center" prop="approval" width="120" /> |
| | | <el-table-column label="审批时间" align="center" prop="approvalTime" width="180" /> |
| | | <el-table-column label="发放时间" align="center" prop="issueTime" width="180" /> |
| | | <el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width" width="200"> |
| | | <el-table-column label="申请时间" |
| | | align="center" |
| | | prop="applyTime" |
| | | width="180" /> |
| | | <el-table-column label="审批人" |
| | | align="center" |
| | | prop="approval" |
| | | width="120" /> |
| | | <el-table-column label="审批时间" |
| | | align="center" |
| | | prop="approvalTime" |
| | | width="180" /> |
| | | <el-table-column label="发放时间" |
| | | align="center" |
| | | prop="issueTime" |
| | | width="180" /> |
| | | <el-table-column label="操作" |
| | | align="center" |
| | | fixed="right" |
| | | class-name="small-padding fixed-width" |
| | | width="200"> |
| | | <template #default="scope"> |
| | | <el-button |
| | | v-if="scope.row.status === 1" |
| | | type="primary" |
| | | link |
| | | @click="handleApprove(scope.row)" |
| | | > |
| | | <el-button v-if="scope.row.status === 1" |
| | | type="primary" |
| | | link |
| | | @click="handleApprove(scope.row)"> |
| | | 审批 |
| | | </el-button> |
| | | <el-button |
| | | v-if="scope.row.status === 3" |
| | | type="success" |
| | | link |
| | | @click="handleIssue(scope.row)" |
| | | > |
| | | <el-button v-if="scope.row.status === 3" |
| | | type="success" |
| | | link |
| | | @click="handleIssue(scope.row)"> |
| | | 发放 |
| | | </el-button> |
| | | <el-button |
| | | type="info" |
| | | link |
| | | @click="handleDetail(scope.row)" |
| | | > |
| | | <el-button type="info" |
| | | link |
| | | @click="handleDetail(scope.row)"> |
| | | 详情 |
| | | </el-button> |
| | | <el-button |
| | | v-if="scope.row.status === 2" |
| | | type="danger" |
| | | link |
| | | @click="handleDelete(scope.row)" |
| | | > |
| | | <el-button v-if="scope.row.status === 2" |
| | | type="danger" |
| | | link |
| | | @click="handleDelete(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" |
| | | /> |
| | | <pagination v-show="total > 0" |
| | | :total="total" |
| | | v-model:page="queryParams.current" |
| | | v-model:limit="queryParams.size" |
| | | @pagination="getList" /> |
| | | </el-card> |
| | | |
| | | <!-- 申请对话框 --> |
| | | <el-dialog |
| | | v-model="showApplyDialog" |
| | | title="办公物资申请" |
| | | width="600px" |
| | | append-to-body |
| | | > |
| | | <el-form ref="applyFormRef" :model="applyForm" :rules="applyRules" label-width="100px"> |
| | | <el-form-item label="申请人" prop="applicant"> |
| | | <el-input v-model="applyForm.applicant" placeholder="请输入申请人名称" /> |
| | | <el-dialog v-model="showApplyDialog" |
| | | title="办公物资申请" |
| | | width="600px" |
| | | append-to-body> |
| | | <el-form ref="applyFormRef" |
| | | :model="applyForm" |
| | | :rules="applyRules" |
| | | label-width="100px"> |
| | | <el-form-item label="申请人" |
| | | prop="applicant"> |
| | | <el-input v-model="applyForm.applicant" |
| | | placeholder="请输入申请人名称" /> |
| | | </el-form-item> |
| | | <el-form-item label="部门" prop="dept"> |
| | | <el-input v-model="applyForm.dept" placeholder="请输入部门名称" /> |
| | | <el-form-item label="部门" |
| | | prop="dept"> |
| | | <el-input v-model="applyForm.dept" |
| | | placeholder="请输入部门名称" /> |
| | | </el-form-item> |
| | | <el-form-item label="物资类型" prop="materialType"> |
| | | <el-select v-model="applyForm.materialType" placeholder="请选择物资类型" style="width: 100%"> |
| | | <el-option label="办公用品" value="4" /> |
| | | <el-option label="电子设备" value="3" /> |
| | | <el-option label="清洁用品" value="2" /> |
| | | <el-option label="其他" value="1" /> |
| | | <el-form-item label="物资类型" |
| | | prop="materialType"> |
| | | <el-select v-model="applyForm.materialType" |
| | | placeholder="请选择物资类型" |
| | | style="width: 100%"> |
| | | <el-option label="办公用品" |
| | | value="4" /> |
| | | <el-option label="电子设备" |
| | | value="3" /> |
| | | <el-option label="清洁用品" |
| | | value="2" /> |
| | | <el-option label="其他" |
| | | value="1" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="具体物品" prop="itemName"> |
| | | <el-input v-model="applyForm.itemName" placeholder="请输入具体物品名称" /> |
| | | <el-form-item label="具体物品" |
| | | prop="itemName"> |
| | | <el-input v-model="applyForm.itemName" |
| | | placeholder="请输入具体物品名称" /> |
| | | </el-form-item> |
| | | <el-form-item label="申请数量" prop="applyNum"> |
| | | <el-input-number v-model="applyForm.applyNum" :min="1" :max="999" style="width: 100%" /> |
| | | <el-form-item label="申请数量" |
| | | prop="applyNum"> |
| | | <el-input-number v-model="applyForm.applyNum" |
| | | :min="1" |
| | | :max="999" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="申请原因" prop="reason"> |
| | | <el-input |
| | | v-model="applyForm.reason" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请输入申请原因" |
| | | /> |
| | | <el-form-item label="申请原因" |
| | | prop="reason"> |
| | | <el-input v-model="applyForm.reason" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请输入申请原因" /> |
| | | </el-form-item> |
| | | <el-form-item label="紧急程度" prop="urgency"> |
| | | <el-form-item label="紧急程度" |
| | | prop="urgency"> |
| | | <el-radio-group v-model="applyForm.urgency"> |
| | | <el-radio label="1">普通</el-radio> |
| | | <el-radio label="2">紧急</el-radio> |
| | |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="submitApply">确 定</el-button> |
| | | <el-button @click="showApplyDialog = false">取 消</el-button> |
| | | <el-button type="primary" @click="submitApply">确 定</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 审批对话框 --> |
| | | <el-dialog |
| | | v-model="showApproveDialog" |
| | | title="审批申请" |
| | | width="500px" |
| | | append-to-body |
| | | > |
| | | <el-form ref="approveFormRef" :model="approveForm" :rules="approveRules" label-width="100px"> |
| | | <el-form-item label="审批结果" prop="approveResult"> |
| | | <el-dialog v-model="showApproveDialog" |
| | | title="审批申请" |
| | | width="500px" |
| | | append-to-body> |
| | | <el-form ref="approveFormRef" |
| | | :model="approveForm" |
| | | :rules="approveRules" |
| | | label-width="100px"> |
| | | <el-form-item label="审批结果" |
| | | prop="approveResult"> |
| | | <el-radio-group v-model="approveForm.approveResult"> |
| | | <el-radio label="3">通过</el-radio> |
| | | <el-radio label="2">拒绝</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | <el-form-item label="审批意见" prop="approvalOpinions"> |
| | | <el-input |
| | | v-model="approveForm.approvalOpinions" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请输入审批意见" |
| | | /> |
| | | <el-form-item label="审批意见" |
| | | prop="approvalOpinions"> |
| | | <el-input v-model="approveForm.approvalOpinions" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请输入审批意见" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="submitApprove">确 定</el-button> |
| | | <el-button @click="showApproveDialog = false">取 消</el-button> |
| | | <el-button type="primary" @click="submitApprove">确 定</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 详情对话框 --> |
| | | <el-dialog |
| | | v-model="showDetailDialog" |
| | | title="申请详情" |
| | | width="700px" |
| | | append-to-body |
| | | > |
| | | <el-descriptions :column="2" border> |
| | | <el-dialog v-model="showDetailDialog" |
| | | title="申请详情" |
| | | width="700px" |
| | | append-to-body> |
| | | <el-descriptions :column="2" |
| | | border> |
| | | <el-descriptions-item label="申请编号">{{ currentDetail.code }}</el-descriptions-item> |
| | | <el-descriptions-item label="申请人">{{ currentDetail.applicant }}</el-descriptions-item> |
| | | <el-descriptions-item label="部门">{{ currentDetail.dept }}</el-descriptions-item> |
| | | <el-descriptions-item label="物资类型">{{ currentDetail.materialType }}</el-descriptions-item> |
| | | <el-descriptions-item label="具体物品">{{ currentDetail.itemName }}</el-descriptions-item> |
| | | <el-descriptions-item label="申请数量">{{ currentDetail.applyNum }}</el-descriptions-item> |
| | | <el-descriptions-item label="申请原因" :span="2">{{ currentDetail.reason }}</el-descriptions-item> |
| | | <el-descriptions-item label="申请原因" |
| | | :span="2">{{ currentDetail.reason }}</el-descriptions-item> |
| | | <el-descriptions-item label="申请状态"> |
| | | <el-tag :type="getStatusType(currentDetail.status)"> |
| | | {{ getStatusText(currentDetail.status) }} |
| | |
| | | <el-descriptions-item label="申请时间">{{ currentDetail.applyTime }}</el-descriptions-item> |
| | | <el-descriptions-item label="审批人">{{ currentDetail.approval || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="审批时间">{{ currentDetail.approvalTime || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="审批意见" :span="2">{{ currentDetail.approvalOpinions || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="审批意见" |
| | | :span="2">{{ currentDetail.approvalOpinions || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="发放时间">{{ currentDetail.issueTime || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="发放人">{{ currentDetail.issueUser || '-' }}</el-descriptions-item> |
| | | </el-descriptions> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {listPage,add,update,deleteOff} from "@/api/collaborativeApproval/officeSupplies.js" |
| | | import {ref, reactive, onMounted, getCurrentInstance} from 'vue' |
| | | import Cookies from 'js-cookie' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { Plus, Search, Refresh, Download, Check } from '@element-plus/icons-vue' |
| | | import { |
| | | listPage, |
| | | add, |
| | | update, |
| | | deleteOff, |
| | | } from "@/api/collaborativeApproval/officeSupplies.js"; |
| | | import { ref, reactive, onMounted, getCurrentInstance } from "vue"; |
| | | import Cookies from "js-cookie"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import { |
| | | Plus, |
| | | Search, |
| | | Refresh, |
| | | Download, |
| | | Check, |
| | | } from "@element-plus/icons-vue"; |
| | | |
| | | // 响应式数据 |
| | | const loading = ref(false) |
| | | const showSearch = ref(true) |
| | | const showApplyDialog = ref(false) |
| | | const showApproveDialog = ref(false) |
| | | const showDetailDialog = ref(false) |
| | | const multipleSelection = ref([]) |
| | | const officeList = ref([]) |
| | | const total = ref(0) |
| | | const suppliesList = ref([]) |
| | | const currentDetail = ref({}) |
| | | // 响应式数据 |
| | | const loading = ref(false); |
| | | const showSearch = ref(true); |
| | | const showApplyDialog = ref(false); |
| | | const showApproveDialog = ref(false); |
| | | const showDetailDialog = ref(false); |
| | | const multipleSelection = ref([]); |
| | | const officeList = ref([]); |
| | | const total = ref(0); |
| | | const suppliesList = ref([]); |
| | | const currentDetail = ref({}); |
| | | |
| | | // 查询参数 |
| | | const queryParams = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | code: '', |
| | | applicant: '', |
| | | status: '' |
| | | }) |
| | | // 查询参数 |
| | | const queryParams = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | code: "", |
| | | applicant: "", |
| | | status: "", |
| | | }); |
| | | |
| | | // 申请表单 |
| | | const applyForm = reactive({ |
| | | applicant: '', |
| | | dept: '', |
| | | materialType: '', |
| | | itemName: '', |
| | | applyNum: 1, |
| | | reason: '', |
| | | urgency: '1' |
| | | }) |
| | | |
| | | // 审批表单 |
| | | const approveForm = reactive({ |
| | | approveResult: '3', |
| | | approvalOpinions: '' |
| | | }) |
| | | |
| | | // 表单校验规则 |
| | | const applyRules = { |
| | | applicant: [{ required: true, message: '请选择物资类型', trigger: 'blur' }], |
| | | dept: [{ required: true, message: '请选择物资类型', trigger: 'blur' }], |
| | | materialType: [{ required: true, message: '请选择物资类型', trigger: 'change' }], |
| | | itemName: [{ required: true, message: '请输入具体物品名称', trigger: 'blur' }], |
| | | applyNum: [{ required: true, message: '请输入申请数量', trigger: 'blur' }], |
| | | reason: [{ required: true, message: '请输入申请原因', trigger: 'blur' }] |
| | | } |
| | | |
| | | const approveRules = { |
| | | approveResult: [{ required: true, message: '请选择审批结果', trigger: 'change' }], |
| | | approvalOpinions: [{ required: true, message: '请输入审批意见', trigger: 'blur' }] |
| | | } |
| | | |
| | | const openShow = () => { |
| | | showApplyDialog.value = true |
| | | resetApplyForm() |
| | | } |
| | | |
| | | // 获取列表数据 |
| | | const getList = () => { |
| | | loading.value = true |
| | | listPage(queryParams).then(res => { |
| | | total.value = res.data.total |
| | | loading.value = false |
| | | officeList.value = res.data.records |
| | | }) |
| | | } |
| | | |
| | | // 查询 |
| | | const handleQuery = () => { |
| | | queryParams.current = 1 |
| | | getList() |
| | | } |
| | | |
| | | // 重置查询 |
| | | const resetQuery = () => { |
| | | queryParams.code = '' |
| | | queryParams.applicant = '' |
| | | queryParams.status = '' |
| | | handleQuery() |
| | | } |
| | | |
| | | // 多选 |
| | | const handleSelectionChange = (selection) => { |
| | | multipleSelection.value = selection |
| | | } |
| | | |
| | | // 获取状态类型 |
| | | const getStatusType = (status) => { |
| | | const statusMap = { |
| | | 1: 'warning', |
| | | 3: 'success', |
| | | 2: 'danger', |
| | | 4: 'info' |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | |
| | | // 获取状态文本 |
| | | const getStatusText = (status) => { |
| | | const statusMap = { |
| | | 1: '待审批', |
| | | 3: '已通过', |
| | | 2: '已拒绝', |
| | | 4: '已发放' |
| | | } |
| | | return statusMap[status] || status |
| | | } |
| | | |
| | | // 提交申请 |
| | | const submitApply = () => { |
| | | add(applyForm).then(() => { |
| | | ElMessage.success('申请成功') |
| | | getList() |
| | | showApplyDialog.value = false |
| | | resetApplyForm() |
| | | }) |
| | | |
| | | |
| | | |
| | | } |
| | | |
| | | //重置表单 |
| | | const resetApplyForm = () => { |
| | | // 重置表单 |
| | | Object.assign(applyForm, { |
| | | applicant: '', |
| | | dept: '', |
| | | materialType: '', |
| | | itemName: '', |
| | | // 申请表单 |
| | | const applyForm = reactive({ |
| | | applicant: "", |
| | | dept: "", |
| | | materialType: "", |
| | | itemName: "", |
| | | applyNum: 1, |
| | | reason: '', |
| | | urgency: '1' |
| | | }) |
| | | } |
| | | reason: "", |
| | | urgency: "1", |
| | | }); |
| | | |
| | | // 审批 |
| | | const handleApprove = (row) => { |
| | | currentDetail.value = row |
| | | showApproveDialog.value = true |
| | | } |
| | | // 审批表单 |
| | | const approveForm = reactive({ |
| | | approveResult: "3", |
| | | approvalOpinions: "", |
| | | }); |
| | | |
| | | const formatDate = (date) => { |
| | | const year = date.getFullYear() |
| | | const month = String(date.getMonth() + 1).padStart(2, '0') |
| | | const day = String(date.getDate()).padStart(2, '0') |
| | | const hours = String(date.getHours()).padStart(2, '0') |
| | | const minutes = String(date.getMinutes()).padStart(2, '0') |
| | | const sends = String(date.getSeconds()).padStart(2, '0') |
| | | return `${year}-${month}-${day} ${hours}:${minutes}:${sends}` |
| | | } |
| | | // 表单校验规则 |
| | | const applyRules = { |
| | | applicant: [{ required: true, message: "请选择物资类型", trigger: "blur" }], |
| | | dept: [{ required: true, message: "请选择物资类型", trigger: "blur" }], |
| | | materialType: [ |
| | | { required: true, message: "请选择物资类型", trigger: "change" }, |
| | | ], |
| | | itemName: [ |
| | | { required: true, message: "请输入具体物品名称", trigger: "blur" }, |
| | | ], |
| | | applyNum: [{ required: true, message: "请输入申请数量", trigger: "blur" }], |
| | | reason: [{ required: true, message: "请输入申请原因", trigger: "blur" }], |
| | | }; |
| | | |
| | | // 提交审批 |
| | | const submitApprove = () => { |
| | | currentDetail.value.status = approveForm.approveResult |
| | | // 从cookie中获取当前登录用户名称 |
| | | currentDetail.value.approval = Cookies.get('username') |
| | | currentDetail.value.approvalTime = formatDate(new Date()) |
| | | currentDetail.value.approvalOpinions = approveForm.approvalOpinions |
| | | update(currentDetail.value).then((res) => { |
| | | if(res.code === 200){ |
| | | showApproveDialog.value = false |
| | | ElMessage.success('审批完成') |
| | | getList() |
| | | const approveRules = { |
| | | approveResult: [ |
| | | { required: true, message: "请选择审批结果", trigger: "change" }, |
| | | ], |
| | | approvalOpinions: [ |
| | | { required: true, message: "请输入审批意见", trigger: "blur" }, |
| | | ], |
| | | }; |
| | | |
| | | // 重置表单 |
| | | Object.assign(approveForm, { |
| | | approveResult: '3', |
| | | approvalOpinions: '' |
| | | }) |
| | | } |
| | | }) |
| | | const openShow = () => { |
| | | showApplyDialog.value = true; |
| | | resetApplyForm(); |
| | | }; |
| | | |
| | | } |
| | | // 获取列表数据 |
| | | const getList = () => { |
| | | loading.value = true; |
| | | listPage(queryParams).then(res => { |
| | | total.value = res.data.total; |
| | | loading.value = false; |
| | | officeList.value = res.data.records; |
| | | }); |
| | | }; |
| | | |
| | | // 发放 |
| | | const handleIssue = (row) => { |
| | | row.status = 4 |
| | | row.issueTime = formatDate(new Date()) |
| | | row.issueUser = Cookies.get('username') |
| | | update(row).then((res) =>{ |
| | | if(res.code === 200){ |
| | | ElMessage.success('发放完成') |
| | | getList() |
| | | } |
| | | }) |
| | | } |
| | | // 查询 |
| | | const handleQuery = () => { |
| | | queryParams.current = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | // 查看详情 |
| | | const handleDetail = (row) => { |
| | | currentDetail.value = row |
| | | showDetailDialog.value = true |
| | | } |
| | | // 重置查询 |
| | | const resetQuery = () => { |
| | | queryParams.code = ""; |
| | | queryParams.applicant = ""; |
| | | queryParams.status = ""; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | // 删除 |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm('确认删除该申请吗?', '提示', { |
| | | confirmButtonText: '确定', |
| | | cancelButtonText: '取消', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | let ids = [row.id] |
| | | deleteOff(ids).then((res) =>{ |
| | | ElMessage.success('删除成功') |
| | | getList() |
| | | // 多选 |
| | | const handleSelectionChange = selection => { |
| | | multipleSelection.value = selection; |
| | | }; |
| | | |
| | | // 获取状态类型 |
| | | const getStatusType = status => { |
| | | const statusMap = { |
| | | 1: "warning", |
| | | 3: "success", |
| | | 2: "danger", |
| | | 4: "info", |
| | | }; |
| | | return statusMap[status] || "info"; |
| | | }; |
| | | |
| | | // 获取状态文本 |
| | | const getStatusText = status => { |
| | | const statusMap = { |
| | | 1: "待审批", |
| | | 3: "已通过", |
| | | 2: "已拒绝", |
| | | 4: "已发放", |
| | | }; |
| | | return statusMap[status] || status; |
| | | }; |
| | | |
| | | // 提交申请 |
| | | const submitApply = () => { |
| | | add(applyForm).then(() => { |
| | | ElMessage.success("申请成功"); |
| | | getList(); |
| | | showApplyDialog.value = false; |
| | | resetApplyForm(); |
| | | }); |
| | | }; |
| | | |
| | | //重置表单 |
| | | const resetApplyForm = () => { |
| | | // 重置表单 |
| | | Object.assign(applyForm, { |
| | | applicant: "", |
| | | dept: "", |
| | | materialType: "", |
| | | itemName: "", |
| | | applyNum: 1, |
| | | reason: "", |
| | | urgency: "1", |
| | | }); |
| | | }; |
| | | |
| | | // 审批 |
| | | const handleApprove = row => { |
| | | currentDetail.value = row; |
| | | showApproveDialog.value = true; |
| | | }; |
| | | |
| | | const formatDate = date => { |
| | | const year = date.getFullYear(); |
| | | const month = String(date.getMonth() + 1).padStart(2, "0"); |
| | | const day = String(date.getDate()).padStart(2, "0"); |
| | | const hours = String(date.getHours()).padStart(2, "0"); |
| | | const minutes = String(date.getMinutes()).padStart(2, "0"); |
| | | const sends = String(date.getSeconds()).padStart(2, "0"); |
| | | return `${year}-${month}-${day} ${hours}:${minutes}:${sends}`; |
| | | }; |
| | | |
| | | // 提交审批 |
| | | const submitApprove = () => { |
| | | currentDetail.value.status = approveForm.approveResult; |
| | | // 从cookie中获取当前登录用户名称 |
| | | currentDetail.value.approval = Cookies.get("username"); |
| | | currentDetail.value.approvalTime = formatDate(new Date()); |
| | | currentDetail.value.approvalOpinions = approveForm.approvalOpinions; |
| | | update(currentDetail.value).then(res => { |
| | | if (res.code === 200) { |
| | | showApproveDialog.value = false; |
| | | ElMessage.success("审批完成"); |
| | | getList(); |
| | | |
| | | // 重置表单 |
| | | Object.assign(approveForm, { |
| | | approveResult: "3", |
| | | approvalOpinions: "", |
| | | }); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // 发放 |
| | | const handleIssue = row => { |
| | | row.status = 4; |
| | | row.issueTime = formatDate(new Date()); |
| | | row.issueUser = Cookies.get("username"); |
| | | update(row).then(res => { |
| | | if (res.code === 200) { |
| | | ElMessage.success("发放完成"); |
| | | getList(); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // 查看详情 |
| | | const handleDetail = row => { |
| | | currentDetail.value = row; |
| | | showDetailDialog.value = true; |
| | | }; |
| | | |
| | | // 删除 |
| | | const handleDelete = row => { |
| | | ElMessageBox.confirm("确认删除该申请吗?", "提示", { |
| | | confirmButtonText: "确定", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }).then(() => { |
| | | let ids = [row.id]; |
| | | deleteOff(ids).then(res => { |
| | | ElMessage.success("删除成功"); |
| | | getList(); |
| | | }); |
| | | }); |
| | | }; |
| | | const { proxy } = getCurrentInstance(); |
| | | // 导出 |
| | | const handleExport = () => { |
| | | ElMessageBox.confirm("所有的内容将被导出,是否确认导出?", "导出", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }) |
| | | }) |
| | | } |
| | | const { proxy } = getCurrentInstance(); |
| | | // 导出 |
| | | const handleExport = () => { |
| | | ElMessageBox.confirm("所有的内容将被导出,是否确认导出?", "导出", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/officeSupplies/export", {}, "办公物资.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已取消"); |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | // 页面加载时获取数据 |
| | | onMounted(() => { |
| | | getList() |
| | | }) |
| | | // 页面加载时获取数据 |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .mb8 { |
| | | margin-bottom: 8px; |
| | | } |
| | | .mb8 { |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | |
| | | :deep(.el-descriptions__label) { |
| | | width: 120px; |
| | | } |
| | | :deep(.el-descriptions__label) { |
| | | width: 120px; |
| | | } |
| | | </style> |
| | |
| | | <!-- 顶部操作栏 --> |
| | | <div class="header-actions"> |
| | | <div class="left-actions"> |
| | | <el-select v-model="currentLevel" placeholder="选择计划级别" style="width: 150px" @change="handleLevelChange"> |
| | | <el-option label="个人计划" value="personal" /> |
| | | <el-option label="小组计划" value="group" /> |
| | | <el-option label="部门计划" value="department" /> |
| | | <el-option label="公司计划" value="company" /> |
| | | <el-select v-model="currentLevel" |
| | | placeholder="选择计划级别" |
| | | style="width: 150px" |
| | | @change="handleLevelChange"> |
| | | <el-option label="个人计划" |
| | | value="personal" /> |
| | | <el-option label="小组计划" |
| | | value="group" /> |
| | | <el-option label="部门计划" |
| | | value="department" /> |
| | | <el-option label="公司计划" |
| | | value="company" /> |
| | | </el-select> |
| | | <el-select v-model="currentPeriod" placeholder="选择时间周期" style="width: 120px; margin-left: 10px" @change="handlePeriodChange"> |
| | | <el-option label="周计划" value="week" /> |
| | | <el-option label="月计划" value="month" /> |
| | | <el-option label="年计划" value="year" /> |
| | | <el-select v-model="currentPeriod" |
| | | placeholder="选择时间周期" |
| | | style="width: 120px; margin-left: 10px" |
| | | @change="handlePeriodChange"> |
| | | <el-option label="周计划" |
| | | value="week" /> |
| | | <el-option label="月计划" |
| | | value="month" /> |
| | | <el-option label="年计划" |
| | | value="year" /> |
| | | </el-select> |
| | | <el-date-picker |
| | | v-model="currentDate" |
| | | :type="datePickerType" |
| | | placeholder="选择日期" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 180px; margin-left: 10px" |
| | | @change="handleDateChange" |
| | | /> |
| | | <el-date-picker v-model="currentDate" |
| | | :type="datePickerType" |
| | | placeholder="选择日期" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 180px; margin-left: 10px" |
| | | @change="handleDateChange" /> |
| | | </div> |
| | | <div class="right-actions"> |
| | | <el-button type="primary" @click="handleAddPlan">新增计划</el-button> |
| | | <el-button type="primary" |
| | | @click="handleAddPlan">新增计划</el-button> |
| | | <el-button @click="handleExport">导出计划</el-button> |
| | | <!-- <el-button @click="handleShare">共享计划@</el-button> --> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 计划概览卡片 --> |
| | | <div class="overview-cards"> |
| | | <el-row :gutter="20"> |
| | |
| | | <el-card class="overview-card"> |
| | | <div class="card-content"> |
| | | <div class="card-icon personal"> |
| | | <el-icon><User /></el-icon> |
| | | <el-icon> |
| | | <User /> |
| | | </el-icon> |
| | | </div> |
| | | <div class="card-info"> |
| | | <div class="card-title">个人计划</div> |
| | | <div class="card-number">{{ overviewData.personal.total }}</div> |
| | | <div class="card-progress"> |
| | | <el-progress :percentage="overviewData.personal.completion" :stroke-width="6" /> |
| | | <el-progress :percentage="overviewData.personal.completion" |
| | | :stroke-width="6" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | <el-card class="overview-card"> |
| | | <div class="card-content"> |
| | | <div class="card-icon group"> |
| | | <el-icon><UserFilled /></el-icon> |
| | | <el-icon> |
| | | <UserFilled /> |
| | | </el-icon> |
| | | </div> |
| | | <div class="card-info"> |
| | | <div class="card-title">小组计划</div> |
| | | <div class="card-number">{{ overviewData.group.total }}</div> |
| | | <div class="card-progress"> |
| | | <el-progress :percentage="overviewData.group.completion" :stroke-width="6" /> |
| | | <el-progress :percentage="overviewData.group.completion" |
| | | :stroke-width="6" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | <el-card class="overview-card"> |
| | | <div class="card-content"> |
| | | <div class="card-icon department"> |
| | | <el-icon><OfficeBuilding /></el-icon> |
| | | <el-icon> |
| | | <OfficeBuilding /> |
| | | </el-icon> |
| | | </div> |
| | | <div class="card-info"> |
| | | <div class="card-title">部门计划</div> |
| | | <div class="card-number">{{ overviewData.department.total }}</div> |
| | | <div class="card-progress"> |
| | | <el-progress :percentage="overviewData.department.completion" :stroke-width="6" /> |
| | | <el-progress :percentage="overviewData.department.completion" |
| | | :stroke-width="6" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | <el-card class="overview-card"> |
| | | <div class="card-content"> |
| | | <div class="card-icon company"> |
| | | <el-icon><House /></el-icon> |
| | | <el-icon> |
| | | <House /> |
| | | </el-icon> |
| | | </div> |
| | | <div class="card-info"> |
| | | <div class="card-title">公司计划</div> |
| | | <div class="card-number">{{ overviewData.company.total }}</div> |
| | | <div class="card-progress"> |
| | | <el-progress :percentage="overviewData.company.completion" :stroke-width="6" /> |
| | | <el-progress :percentage="overviewData.company.completion" |
| | | :stroke-width="6" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- 计划列表 --> |
| | | <div class="plan-content"> |
| | | <el-card> |
| | |
| | | <div class="card-header"> |
| | | <span>{{ getCurrentLevelText() }} - {{ getCurrentPeriodText() }}</span> |
| | | <div> |
| | | <el-button size="small" @click="handleRefresh">刷新</el-button> |
| | | <el-button size="small" |
| | | @click="handleRefresh">刷新</el-button> |
| | | <!-- <el-button size="small" @click="handleFilter">筛选@</el-button> --> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <div class="plan-list"> |
| | | <div v-for="plan in planList" :key="plan.id" class="plan-item"> |
| | | <div v-for="plan in planList" |
| | | :key="plan.id" |
| | | class="plan-item"> |
| | | <div class="plan-header"> |
| | | <div class="plan-title"> |
| | | <el-tag :type="getPriorityType(plan.priority)" size="small">{{ getPriorityText(plan.priority) }}</el-tag> |
| | | <el-tag :type="getPriorityType(plan.priority)" |
| | | size="small">{{ getPriorityText(plan.priority) }}</el-tag> |
| | | <span class="title-text">{{ plan.title }}</span> |
| | | </div> |
| | | <div class="plan-actions"> |
| | | <el-button size="small" @click="handleEditPlan(plan)">编辑</el-button> |
| | | <el-button size="small" @click="handleViewDetail(plan)">详情</el-button> |
| | | <el-button size="small" |
| | | @click="handleEditPlan(plan)">编辑</el-button> |
| | | <el-button size="small" |
| | | @click="handleViewDetail(plan)">详情</el-button> |
| | | <el-dropdown @command="(command) => handleMoreAction(plan, command)"> |
| | | <el-button size="small"> |
| | | 更多<el-icon class="el-icon--right"><ArrowDown /></el-icon> |
| | | 更多<el-icon class="el-icon--right"> |
| | | <ArrowDown /> |
| | | </el-icon> |
| | | </el-button> |
| | | <template #dropdown> |
| | | <el-dropdown-menu> |
| | | <!-- <el-dropdown-item command="share">共享@</el-dropdown-item> --> |
| | | <el-dropdown-item command="copy">复制</el-dropdown-item> |
| | | <el-dropdown-item command="delete" divided>删除</el-dropdown-item> |
| | | <el-dropdown-item command="delete" |
| | | divided>删除</el-dropdown-item> |
| | | </el-dropdown-menu> |
| | | </template> |
| | | </el-dropdown> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="plan-content"> |
| | | <div class="plan-description">{{ plan.description }}</div> |
| | | <div class="plan-meta"> |
| | | <div class="meta-item"> |
| | | <el-icon><Calendar /></el-icon> |
| | | <el-icon> |
| | | <Calendar /> |
| | | </el-icon> |
| | | <span>{{ plan.startDate }} - {{ plan.endDate }}</span> |
| | | </div> |
| | | <div class="meta-item"> |
| | | <el-icon><User /></el-icon> |
| | | <el-icon> |
| | | <User /> |
| | | </el-icon> |
| | | <span>{{ plan.assignee }}</span> |
| | | </div> |
| | | <div class="meta-item"> |
| | | <el-icon><Clock /></el-icon> |
| | | <el-icon> |
| | | <Clock /> |
| | | </el-icon> |
| | | <span>进度: {{ plan.progress }}%</span> |
| | | </div> |
| | | <div class="meta-item"> |
| | | <el-icon><Flag /></el-icon> |
| | | <el-icon> |
| | | <Flag /> |
| | | </el-icon> |
| | | <span>{{ getStatusText(plan.status) }}</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="plan-progress"> |
| | | <el-progress |
| | | :percentage="plan.progress" |
| | | :color="getProgressColor(plan.progress)" |
| | | :stroke-width="8" |
| | | /> |
| | | <el-progress :percentage="plan.progress" |
| | | :color="getProgressColor(plan.progress)" |
| | | :stroke-width="8" /> |
| | | </div> |
| | | |
| | | <div class="plan-tags"> |
| | | <el-tag v-for="tag in plan.tags" :key="tag" size="small" style="margin-right: 5px"> |
| | | <el-tag v-for="tag in plan.tags" |
| | | :key="tag" |
| | | size="small" |
| | | style="margin-right: 5px"> |
| | | {{ tag }} |
| | | </el-tag> |
| | | </div> |
| | |
| | | </div> |
| | | </el-card> |
| | | </div> |
| | | |
| | | <!-- 新增/编辑计划对话框 --> |
| | | <el-dialog |
| | | v-model="planDialogVisible" |
| | | :title="operationType === 'add' ? '发布计划' : '编辑计划'" |
| | | width="600px" |
| | | @close="handleDialogClose" |
| | | > |
| | | <el-form :model="planForm" :rules="planRules" ref="planFormRef" label-width="100px"> |
| | | <el-form-item label="计划标题" prop="title"> |
| | | <el-input v-model="planForm.title" placeholder="请输入计划标题" /> |
| | | <el-dialog v-model="planDialogVisible" |
| | | :title="operationType === 'add' ? '发布计划' : '编辑计划'" |
| | | width="600px" |
| | | @close="handleDialogClose"> |
| | | <el-form :model="planForm" |
| | | :rules="planRules" |
| | | ref="planFormRef" |
| | | label-width="100px"> |
| | | <el-form-item label="计划标题" |
| | | prop="title"> |
| | | <el-input v-model="planForm.title" |
| | | placeholder="请输入计划标题" /> |
| | | </el-form-item> |
| | | <el-form-item label="计划描述" prop="description"> |
| | | <el-input |
| | | v-model="planForm.description" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请输入计划描述" |
| | | /> |
| | | <el-form-item label="计划描述" |
| | | prop="description"> |
| | | <el-input v-model="planForm.description" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请输入计划描述" /> |
| | | </el-form-item> |
| | | <el-form-item label="计划级别" prop="level"> |
| | | <el-select v-model="planForm.level" placeholder="选择计划级别" style="width: 100%"> |
| | | <el-option label="个人计划" value="personal" /> |
| | | <el-option label="小组计划" value="group" /> |
| | | <el-option label="部门计划" value="department" /> |
| | | <el-option label="公司计划" value="company" /> |
| | | <el-form-item label="计划级别" |
| | | prop="level"> |
| | | <el-select v-model="planForm.level" |
| | | placeholder="选择计划级别" |
| | | style="width: 100%"> |
| | | <el-option label="个人计划" |
| | | value="personal" /> |
| | | <el-option label="小组计划" |
| | | value="group" /> |
| | | <el-option label="部门计划" |
| | | value="department" /> |
| | | <el-option label="公司计划" |
| | | value="company" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="时间周期" prop="period"> |
| | | <el-select v-model="planForm.period" placeholder="选择时间周期" style="width: 100%"> |
| | | <el-option label="周计划" value="week" /> |
| | | <el-option label="月计划" value="month" /> |
| | | <el-option label="年计划" value="year" /> |
| | | <el-form-item label="时间周期" |
| | | prop="period"> |
| | | <el-select v-model="planForm.period" |
| | | placeholder="选择时间周期" |
| | | style="width: 100%"> |
| | | <el-option label="周计划" |
| | | value="week" /> |
| | | <el-option label="月计划" |
| | | value="month" /> |
| | | <el-option label="年计划" |
| | | value="year" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="开始时间" prop="startDate"> |
| | | <el-date-picker |
| | | v-model="planForm.startDate" |
| | | type="date" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | placeholder="选择开始时间" |
| | | style="width: 100%" |
| | | /> |
| | | <el-form-item label="开始时间" |
| | | prop="startDate"> |
| | | <el-date-picker v-model="planForm.startDate" |
| | | type="date" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | placeholder="选择开始时间" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="结束时间" prop="endDate"> |
| | | <el-date-picker |
| | | v-model="planForm.endDate" |
| | | type="date" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | placeholder="选择结束时间" |
| | | style="width: 100%" |
| | | /> |
| | | <el-form-item label="结束时间" |
| | | prop="endDate"> |
| | | <el-date-picker v-model="planForm.endDate" |
| | | type="date" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | placeholder="选择结束时间" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="负责人" prop="assignee"> |
| | | <el-input v-model="planForm.assignee" placeholder="请输入负责人" /> |
| | | <el-form-item label="负责人" |
| | | prop="assignee"> |
| | | <el-input v-model="planForm.assignee" |
| | | placeholder="请输入负责人" /> |
| | | </el-form-item> |
| | | <el-form-item label="优先级" prop="priority"> |
| | | <el-select v-model="planForm.priority" placeholder="选择优先级" style="width: 100%"> |
| | | <el-option label="高" value="high" /> |
| | | <el-option label="中" value="medium" /> |
| | | <el-option label="低" value="low" /> |
| | | <el-form-item label="优先级" |
| | | prop="priority"> |
| | | <el-select v-model="planForm.priority" |
| | | placeholder="选择优先级" |
| | | style="width: 100%"> |
| | | <el-option label="高" |
| | | value="high" /> |
| | | <el-option label="中" |
| | | value="medium" /> |
| | | <el-option label="低" |
| | | value="low" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <!-- <el-form-item label="标签"> |
| | | <el-input v-model="planForm.tags" placeholder="请输入标签,用逗号分隔" /> |
| | | </el-form-item> --> |
| | | <el-form-item label="标签" prop="tags"> |
| | | <el-form-item label="标签" |
| | | prop="tags"> |
| | | <!-- <el-checkbox-group v-model="planForm.tags"> |
| | | <el-checkbox label="all"></el-checkbox> |
| | | <el-checkbox label="manager">管理层</el-checkbox> |
| | |
| | | <el-checkbox label="finance">财务部门</el-checkbox> |
| | | <el-checkbox label="tech">技术部门</el-checkbox> |
| | | </el-checkbox-group> --> |
| | | <el-select |
| | | v-model="planForm.tags" |
| | | multiple |
| | | placeholder="请选择标签" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="dept in departments" |
| | | :key="dept" |
| | | :label="dept" |
| | | :value="dept" |
| | | /> |
| | | <el-select v-model="planForm.tags" |
| | | multiple |
| | | placeholder="请选择标签" |
| | | style="width: 100%"> |
| | | <el-option v-for="dept in departments" |
| | | :key="dept" |
| | | :label="dept" |
| | | :value="dept" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="状态" prop="status"> |
| | | <el-select v-model="planForm.status" placeholder="选择状态" style="width: 100%"> |
| | | <el-option label="未开始" value="not_started" /> |
| | | <el-option label="进行中" value="in_progress" /> |
| | | <el-option label="已完成" value="completed" /> |
| | | <el-option label="已暂停" value="paused" /> |
| | | <el-form-item label="状态" |
| | | prop="status"> |
| | | <el-select v-model="planForm.status" |
| | | placeholder="选择状态" |
| | | style="width: 100%"> |
| | | <el-option label="未开始" |
| | | value="not_started" /> |
| | | <el-option label="进行中" |
| | | value="in_progress" /> |
| | | <el-option label="已完成" |
| | | value="completed" /> |
| | | <el-option label="已暂停" |
| | | value="paused" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="进度" prop="progress"> |
| | | <el-input-number |
| | | v-model="planForm.progress" |
| | | min="0" |
| | | max="100" |
| | | step="1" |
| | | placeholder="请输入进度" |
| | | style="width: 100%" |
| | | /> |
| | | <el-form-item label="进度" |
| | | prop="progress"> |
| | | <el-input-number v-model="planForm.progress" |
| | | min="0" |
| | | max="100" |
| | | step="1" |
| | | placeholder="请输入进度" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="handleSavePlan">保存</el-button> |
| | | <el-button @click="planDialogVisible = false">取消</el-button> |
| | | <el-button type="primary" @click="handleSavePlan">保存</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | <!-- 计划详情对话框 --> |
| | | <el-dialog v-model="showPlanDetailDialog" title="计划详情" width="700px"> |
| | | <div v-if="currentPlanDetail" class="mb10"> |
| | | <el-descriptions :column="2" border> |
| | | <el-dialog v-model="showPlanDetailDialog" |
| | | title="计划详情" |
| | | width="700px"> |
| | | <div v-if="currentPlanDetail" |
| | | class="mb10"> |
| | | <el-descriptions :column="2" |
| | | border> |
| | | <el-descriptions-item label="计划标题">{{ currentPlanDetail.title }}</el-descriptions-item> |
| | | <el-descriptions-item label="计划描述">{{ currentPlanDetail.description }}</el-descriptions-item> |
| | | <el-descriptions-item label="计划级别">{{ getCurrentLevelText(currentPlanDetail.level) }}</el-descriptions-item> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed, onMounted } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | const { proxy } = getCurrentInstance(); |
| | | import { |
| | | User, |
| | | UserFilled, |
| | | OfficeBuilding, |
| | | House, |
| | | Calendar, |
| | | Clock, |
| | | Flag, |
| | | ArrowDown |
| | | } from '@element-plus/icons-vue' |
| | | import { listDutyPlan, addDutyPlan, updateDutyPlan, delDutyPlan,NumDutyPlan,exportDutyPlan } from '@/api/collaborativeApproval/planTemplate.js' |
| | | import { ref, reactive, computed, onMounted } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | const { proxy } = getCurrentInstance(); |
| | | import { |
| | | User, |
| | | UserFilled, |
| | | OfficeBuilding, |
| | | House, |
| | | Calendar, |
| | | Clock, |
| | | Flag, |
| | | ArrowDown, |
| | | } from "@element-plus/icons-vue"; |
| | | import { |
| | | listDutyPlan, |
| | | addDutyPlan, |
| | | updateDutyPlan, |
| | | delDutyPlan, |
| | | NumDutyPlan, |
| | | exportDutyPlan, |
| | | } from "@/api/collaborativeApproval/planTemplate.js"; |
| | | |
| | | // 响应式数据 |
| | | const operationType = ref('add') |
| | | const currentLevel = ref('personal') |
| | | const currentPeriod = ref('week') |
| | | const currentDate = ref(new Date()) |
| | | const planDialogVisible = ref(false) |
| | | const dialogTitle = ref('新增计划') |
| | | const planFormRef = ref() |
| | | const showPlanDetailDialog = ref(false) |
| | | const currentPlanDetail = ref(null) |
| | | // 响应式数据 |
| | | const operationType = ref("add"); |
| | | const currentLevel = ref("personal"); |
| | | const currentPeriod = ref("week"); |
| | | const currentDate = ref(new Date()); |
| | | const planDialogVisible = ref(false); |
| | | const dialogTitle = ref("新增计划"); |
| | | const planFormRef = ref(); |
| | | const showPlanDetailDialog = ref(false); |
| | | const currentPlanDetail = ref(null); |
| | | |
| | | // 表单数据 |
| | | const planForm = reactive({ |
| | | id: '', |
| | | title: '', |
| | | description: '', |
| | | level: 'personal', |
| | | period: 'week', |
| | | startDate: '', |
| | | endDate: '', |
| | | assignee: '', |
| | | priority: 'medium', |
| | | tags: [], |
| | | status: '', |
| | | progress: 0 |
| | | }) |
| | | // 表单数据 |
| | | const planForm = reactive({ |
| | | id: "", |
| | | title: "", |
| | | description: "", |
| | | level: "personal", |
| | | period: "week", |
| | | startDate: "", |
| | | endDate: "", |
| | | assignee: "", |
| | | priority: "medium", |
| | | tags: [], |
| | | status: "", |
| | | progress: 0, |
| | | }); |
| | | |
| | | // 表单验证规则 |
| | | const planRules = { |
| | | title: [{ required: true, message: '请输入计划标题', trigger: 'blur' }], |
| | | description: [{ required: true, message: '请输入计划描述', trigger: 'blur' }], |
| | | level: [{ required: true, message: '请选择计划级别', trigger: 'change' }], |
| | | period: [{ required: true, message: '请选择时间周期', trigger: 'change' }], |
| | | startDate: [{ required: true, message: '请选择开始时间', trigger: 'change' }], |
| | | endDate: [{ required: true, message: '请选择结束时间', trigger: 'change' }], |
| | | assignee: [{ required: true, message: '请输入负责人', trigger: 'blur' }], |
| | | priority: [{ required: true, message: '请选择优先级', trigger: 'change' }] |
| | | } |
| | | const departments = ["产品", "分析", "调研",'技术', '架构', '设计','市场', '推广', '营销']; |
| | | // 概览数据 |
| | | const overviewData = reactive({ |
| | | personal: { total: 0, completion: 0 }, |
| | | group: { total: 0, completion: 0 }, |
| | | department: { total: 0, completion: 0 }, |
| | | company: { total: 0, completion: 0 } |
| | | }) |
| | | // 表单验证规则 |
| | | const planRules = { |
| | | title: [{ required: true, message: "请输入计划标题", trigger: "blur" }], |
| | | description: [{ required: true, message: "请输入计划描述", trigger: "blur" }], |
| | | level: [{ required: true, message: "请选择计划级别", trigger: "change" }], |
| | | period: [{ required: true, message: "请选择时间周期", trigger: "change" }], |
| | | startDate: [{ required: true, message: "请选择开始时间", trigger: "change" }], |
| | | endDate: [{ required: true, message: "请选择结束时间", trigger: "change" }], |
| | | assignee: [{ required: true, message: "请输入负责人", trigger: "blur" }], |
| | | priority: [{ required: true, message: "请选择优先级", trigger: "change" }], |
| | | }; |
| | | const departments = [ |
| | | "产品", |
| | | "分析", |
| | | "调研", |
| | | "技术", |
| | | "架构", |
| | | "设计", |
| | | "市场", |
| | | "推广", |
| | | "营销", |
| | | ]; |
| | | // 概览数据 |
| | | const overviewData = reactive({ |
| | | personal: { total: 0, completion: 0 }, |
| | | group: { total: 0, completion: 0 }, |
| | | department: { total: 0, completion: 0 }, |
| | | company: { total: 0, completion: 0 }, |
| | | }); |
| | | |
| | | // 计划列表数据 |
| | | const planList = ref([]) |
| | | // 计划列表数据 |
| | | const planList = ref([]); |
| | | |
| | | // 计算属性 |
| | | const datePickerType = computed(() => { |
| | | switch (currentPeriod.value) { |
| | | case 'week': |
| | | return 'week' |
| | | case 'month': |
| | | return 'month' |
| | | case 'year': |
| | | return 'year' |
| | | default: |
| | | return 'date' |
| | | } |
| | | }) |
| | | // 计算属性 |
| | | const datePickerType = computed(() => { |
| | | switch (currentPeriod.value) { |
| | | case "week": |
| | | return "week"; |
| | | case "month": |
| | | return "month"; |
| | | case "year": |
| | | return "year"; |
| | | default: |
| | | return "date"; |
| | | } |
| | | }); |
| | | |
| | | // 方法 |
| | | const handleLevelChange = (value) => { |
| | | console.log('计划级别变更:', value) |
| | | getPlanList() |
| | | // 这里可以根据级别筛选数据 |
| | | } |
| | | // 方法 |
| | | const handleLevelChange = value => { |
| | | console.log("计划级别变更:", value); |
| | | getPlanList(); |
| | | // 这里可以根据级别筛选数据 |
| | | }; |
| | | |
| | | const handlePeriodChange = (value) => { |
| | | console.log('时间周期变更:', value) |
| | | getPlanList() |
| | | // 这里可以根据周期筛选数据 |
| | | } |
| | | const handlePeriodChange = value => { |
| | | console.log("时间周期变更:", value); |
| | | getPlanList(); |
| | | // 这里可以根据周期筛选数据 |
| | | }; |
| | | |
| | | const handleDateChange = (value) => { |
| | | console.log('日期变更:', value) |
| | | getPlanList() |
| | | // 这里可以根据日期筛选数据 |
| | | } |
| | | const handleDateChange = value => { |
| | | console.log("日期变更:", value); |
| | | getPlanList(); |
| | | // 这里可以根据日期筛选数据 |
| | | }; |
| | | |
| | | const handleAddPlan = () => { |
| | | operationType.value = 'add' |
| | | dialogTitle.value = '新增计划' |
| | | planDialogVisible.value = true |
| | | // 重置表单 |
| | | Object.keys(planForm).forEach(key => { |
| | | planForm[key] = '' |
| | | }) |
| | | planForm.level = 'personal' |
| | | planForm.period = 'week' |
| | | planForm.priority = 'medium' |
| | | planForm.status = 'not_started' |
| | | planForm.progress = 0 |
| | | } |
| | | const handleAddPlan = () => { |
| | | operationType.value = "add"; |
| | | dialogTitle.value = "新增计划"; |
| | | planDialogVisible.value = true; |
| | | // 重置表单 |
| | | Object.keys(planForm).forEach(key => { |
| | | planForm[key] = ""; |
| | | }); |
| | | planForm.level = "personal"; |
| | | planForm.period = "week"; |
| | | planForm.priority = "medium"; |
| | | planForm.status = "not_started"; |
| | | planForm.progress = 0; |
| | | }; |
| | | |
| | | const handleEditPlan = (plan) => { |
| | | operationType.value = 'edit' |
| | | dialogTitle.value = '编辑计划' |
| | | planDialogVisible.value = true |
| | | Object.assign(planForm, plan) |
| | | // // 填充表单数据 |
| | | // Object.keys(planForm).forEach(key => { |
| | | // if (key === 'tags') { |
| | | // planForm[key] = plan[key].join(', ') |
| | | // } else { |
| | | // planForm[key] = plan[key] |
| | | // } |
| | | // }) |
| | | } |
| | | const handleEditPlan = plan => { |
| | | operationType.value = "edit"; |
| | | dialogTitle.value = "编辑计划"; |
| | | planDialogVisible.value = true; |
| | | Object.assign(planForm, plan); |
| | | // // 填充表单数据 |
| | | // Object.keys(planForm).forEach(key => { |
| | | // if (key === 'tags') { |
| | | // planForm[key] = plan[key].join(', ') |
| | | // } else { |
| | | // planForm[key] = plan[key] |
| | | // } |
| | | // }) |
| | | }; |
| | | |
| | | const handleViewDetail = (plan) => { |
| | | currentPlanDetail.value = plan |
| | | showPlanDetailDialog.value = true |
| | | // ElMessage.info(`查看计划详情: ${plan.title}`) |
| | | } |
| | | const handleViewDetail = plan => { |
| | | currentPlanDetail.value = plan; |
| | | showPlanDetailDialog.value = true; |
| | | // ElMessage.info(`查看计划详情: ${plan.title}`) |
| | | }; |
| | | |
| | | const handleMoreAction = async(plan,command) => { |
| | | let ids = []; |
| | | ids.push(plan.id); |
| | | console.log("ids",ids) |
| | | switch (command) { |
| | | case 'share': |
| | | ElMessage.success('计划已共享') |
| | | break |
| | | case 'copy': |
| | | const knowledgeText = ` |
| | | 计划标题:${plan.title} |
| | | 计划描述:${plan.description} |
| | | 计划级别:${getCurrentLevelText(plan.level)} |
| | | 时间周期:${getCurrentPeriodText(plan.period)} |
| | | 开始时间:${plan.startDate} |
| | | 结束时间:${plan.endDate} |
| | | 负责人:${plan.assignee} |
| | | 优先级:${getPriorityText(plan.priority)} |
| | | 标签:${plan.tags.join(', ')} |
| | | 状态:${getStatusText(plan.status)} |
| | | 进度:${plan.progress}% |
| | | `.trim(); |
| | | const handleMoreAction = async (plan, command) => { |
| | | let ids = []; |
| | | ids.push(plan.id); |
| | | console.log("ids", ids); |
| | | switch (command) { |
| | | case "share": |
| | | ElMessage.success("计划已共享"); |
| | | break; |
| | | case "copy": |
| | | const knowledgeText = ` |
| | | 计划标题:${plan.title} |
| | | 计划描述:${plan.description} |
| | | 计划级别:${getCurrentLevelText(plan.level)} |
| | | 时间周期:${getCurrentPeriodText(plan.period)} |
| | | 开始时间:${plan.startDate} |
| | | 结束时间:${plan.endDate} |
| | | 负责人:${plan.assignee} |
| | | 优先级:${getPriorityText(plan.priority)} |
| | | 标签:${plan.tags.join(", ")} |
| | | 状态:${getStatusText(plan.status)} |
| | | 进度:${plan.progress}% |
| | | `.trim(); |
| | | |
| | | // 复制到剪贴板 |
| | | navigator.clipboard.writeText(knowledgeText).then(() => { |
| | | ElMessage.success("知识内容已复制到剪贴板"); |
| | | }).catch(() => { |
| | | ElMessage.error("复制失败,请手动复制"); |
| | | navigator.clipboard |
| | | .writeText(knowledgeText) |
| | | .then(() => { |
| | | ElMessage.success("知识内容已复制到剪贴板"); |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error("复制失败,请手动复制"); |
| | | }); |
| | | // ElMessage.success('计划已复制') |
| | | break; |
| | | case "delete": |
| | | ElMessageBox.confirm("确定要删除这个计划吗?", "提示", { |
| | | confirmButtonText: "确定", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }).then(() => { |
| | | delDutyPlan(ids).then(res => { |
| | | if (res.code === 200) { |
| | | ElMessage.success("计划已删除"); |
| | | ids.value = []; |
| | | getPlanList(); |
| | | } |
| | | }); |
| | | }); |
| | | // ElMessage.success('计划已复制') |
| | | break |
| | | case 'delete': |
| | | ElMessageBox.confirm('确定要删除这个计划吗?', '提示', { |
| | | confirmButtonText: '确定', |
| | | cancelButtonText: '取消', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | |
| | | delDutyPlan(ids).then(res => { |
| | | break; |
| | | } |
| | | }; |
| | | // |
| | | const handleSavePlan = async () => { |
| | | try { |
| | | await planFormRef.value.validate(); |
| | | if (operationType.value === "add") { |
| | | addDutyPlan(planForm).then(res => { |
| | | if (res.code === 200) { |
| | | ElMessage.success('计划已删除') |
| | | ids.value = []; |
| | | getPlanList() |
| | | ElMessage.success("计划保存成功"); |
| | | planDialogVisible.value = false; |
| | | } |
| | | }) |
| | | }) |
| | | break |
| | | } |
| | | } |
| | | // |
| | | const handleSavePlan = async () => { |
| | | try { |
| | | await planFormRef.value.validate() |
| | | if (operationType.value === 'add') { |
| | | addDutyPlan(planForm).then(res => { |
| | | if (res.code === 200) { |
| | | ElMessage.success('计划保存成功') |
| | | planDialogVisible.value = false |
| | | } |
| | | getPlanList() |
| | | }) |
| | | } else { |
| | | |
| | | updateDutyPlan(planForm).then(res => { |
| | | if (res.code === 200) { |
| | | ElMessage.success('计划保存成功') |
| | | planDialogVisible.value = false |
| | | } |
| | | getPlanList() |
| | | }) |
| | | getPlanList(); |
| | | }); |
| | | } else { |
| | | updateDutyPlan(planForm).then(res => { |
| | | if (res.code === 200) { |
| | | ElMessage.success("计划保存成功"); |
| | | planDialogVisible.value = false; |
| | | } |
| | | getPlanList(); |
| | | }); |
| | | } |
| | | } catch (error) { |
| | | console.log("表单验证失败:", error); |
| | | } |
| | | } catch (error) { |
| | | console.log('表单验证失败:', error) |
| | | } |
| | | } |
| | | }; |
| | | |
| | | const handleDialogClose = () => { |
| | | planFormRef.value?.resetFields() |
| | | } |
| | | const handleDialogClose = () => { |
| | | planFormRef.value?.resetFields(); |
| | | }; |
| | | |
| | | const handleRefresh = () => { |
| | | getPlanList() |
| | | // ElMessage.success('数据已刷新') |
| | | } |
| | | const handleRefresh = () => { |
| | | getPlanList(); |
| | | // ElMessage.success('数据已刷新') |
| | | }; |
| | | |
| | | const handleFilter = () => { |
| | | ElMessage.info('打开筛选面板') |
| | | } |
| | | const handleFilter = () => { |
| | | ElMessage.info("打开筛选面板"); |
| | | }; |
| | | |
| | | const handleExport = () => { |
| | | ElMessageBox.confirm("是否确认导出?", "导出", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | // exportDutyPlan().then(res => { |
| | | |
| | | // }) |
| | | proxy.download("/dutyPlan/export", {}, "计划管理.xlsx"); |
| | | const handleExport = () => { |
| | | ElMessageBox.confirm("是否确认导出?", "导出", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已取消"); |
| | | }); |
| | | }; |
| | | const handleShare = () => { |
| | | ElMessage.success('计划已共享') |
| | | } |
| | | .then(() => { |
| | | // exportDutyPlan().then(res => { |
| | | |
| | | const getCurrentLevelText = () => { |
| | | const levelMap = { |
| | | personal: '个人计划', |
| | | group: '小组计划', |
| | | department: '部门计划', |
| | | company: '公司计划' |
| | | } |
| | | return levelMap[currentLevel.value] || '个人计划' |
| | | } |
| | | |
| | | const getCurrentPeriodText = () => { |
| | | const periodMap = { |
| | | week: '周计划', |
| | | month: '月计划', |
| | | year: '年计划' |
| | | } |
| | | return periodMap[currentPeriod.value] || '周计划' |
| | | } |
| | | |
| | | const getPriorityType = (priority) => { |
| | | const typeMap = { |
| | | high: 'danger', |
| | | medium: 'warning', |
| | | low: 'info' |
| | | } |
| | | return typeMap[priority] || 'info' |
| | | } |
| | | |
| | | const getPriorityText = (priority) => { |
| | | const textMap = { |
| | | high: '高', |
| | | medium: '中', |
| | | low: '低' |
| | | } |
| | | return textMap[priority] || '中' |
| | | } |
| | | |
| | | const getStatusText = (status) => { |
| | | const statusMap = { |
| | | not_started: '未开始', |
| | | in_progress: '进行中', |
| | | completed: '已完成', |
| | | paused: '已暂停' |
| | | } |
| | | return statusMap[status] || '未知' |
| | | } |
| | | |
| | | const getProgressColor = (progress) => { |
| | | if (progress >= 80) return '#67C23A' |
| | | if (progress >= 50) return '#E6A23C' |
| | | return '#F56C6C' |
| | | } |
| | | //获取数据列表 |
| | | const getPlanList = async () => { |
| | | const params = { |
| | | level: currentLevel.value, |
| | | period: currentPeriod.value, |
| | | queryDate:currentDate.value |
| | | } |
| | | listDutyPlan(params).then(res => { |
| | | if (res.code === 200) { |
| | | planList.value = res.data.records |
| | | } |
| | | }).catch(err => { |
| | | console.log(err) |
| | | }) |
| | | } |
| | | //获取数据 |
| | | const getPlanNum = async () => { |
| | | NumDutyPlan().then(res => { |
| | | if (res.code === 200) { |
| | | // console.log(res.data) |
| | | //讲结果里面的数据根据level 赋值给overviewData |
| | | res.data.forEach(item => { |
| | | overviewData[item.level].total = item.num |
| | | overviewData[item.level].completion = item.completion |
| | | // }) |
| | | proxy.download("/dutyPlan/export", {}, "计划管理.xlsx"); |
| | | }) |
| | | |
| | | } |
| | | }).catch(err => { |
| | | console.log(err) |
| | | }) |
| | | } |
| | | .catch(() => { |
| | | proxy.$modal.msg("已取消"); |
| | | }); |
| | | }; |
| | | const handleShare = () => { |
| | | ElMessage.success("计划已共享"); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getPlanList() |
| | | getPlanNum() |
| | | console.log('多级计划模板页面已加载') |
| | | }) |
| | | const getCurrentLevelText = () => { |
| | | const levelMap = { |
| | | personal: "个人计划", |
| | | group: "小组计划", |
| | | department: "部门计划", |
| | | company: "公司计划", |
| | | }; |
| | | return levelMap[currentLevel.value] || "个人计划"; |
| | | }; |
| | | |
| | | const getCurrentPeriodText = () => { |
| | | const periodMap = { |
| | | week: "周计划", |
| | | month: "月计划", |
| | | year: "年计划", |
| | | }; |
| | | return periodMap[currentPeriod.value] || "周计划"; |
| | | }; |
| | | |
| | | const getPriorityType = priority => { |
| | | const typeMap = { |
| | | high: "danger", |
| | | medium: "warning", |
| | | low: "info", |
| | | }; |
| | | return typeMap[priority] || "info"; |
| | | }; |
| | | |
| | | const getPriorityText = priority => { |
| | | const textMap = { |
| | | high: "高", |
| | | medium: "中", |
| | | low: "低", |
| | | }; |
| | | return textMap[priority] || "中"; |
| | | }; |
| | | |
| | | const getStatusText = status => { |
| | | const statusMap = { |
| | | not_started: "未开始", |
| | | in_progress: "进行中", |
| | | completed: "已完成", |
| | | paused: "已暂停", |
| | | }; |
| | | return statusMap[status] || "未知"; |
| | | }; |
| | | |
| | | const getProgressColor = progress => { |
| | | if (progress >= 80) return "#67C23A"; |
| | | if (progress >= 50) return "#E6A23C"; |
| | | return "#F56C6C"; |
| | | }; |
| | | //获取数据列表 |
| | | const getPlanList = async () => { |
| | | const params = { |
| | | level: currentLevel.value, |
| | | period: currentPeriod.value, |
| | | queryDate: currentDate.value, |
| | | }; |
| | | listDutyPlan(params) |
| | | .then(res => { |
| | | if (res.code === 200) { |
| | | planList.value = res.data.records; |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | console.log(err); |
| | | }); |
| | | }; |
| | | //获取数据 |
| | | const getPlanNum = async () => { |
| | | NumDutyPlan() |
| | | .then(res => { |
| | | if (res.code === 200) { |
| | | // console.log(res.data) |
| | | //讲结果里面的数据根据level 赋值给overviewData |
| | | res.data.forEach(item => { |
| | | overviewData[item.level].total = item.num; |
| | | overviewData[item.level].completion = item.completion; |
| | | }); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | console.log(err); |
| | | }); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getPlanList(); |
| | | getPlanNum(); |
| | | console.log("多级计划模板页面已加载"); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | background-color: #f5f5f5; |
| | | min-height: 100vh; |
| | | } |
| | | .app-container { |
| | | padding: 20px; |
| | | background-color: #f5f5f5; |
| | | min-height: 100vh; |
| | | } |
| | | |
| | | .header-actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | background: white; |
| | | padding: 20px; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .left-actions { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .right-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .overview-cards { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .overview-card { |
| | | height: 120px; |
| | | } |
| | | |
| | | .card-content { |
| | | display: flex; |
| | | align-items: center; |
| | | height: 100%; |
| | | } |
| | | |
| | | .card-icon { |
| | | width: 60px; |
| | | height: 60px; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin-right: 15px; |
| | | font-size: 24px; |
| | | color: white; |
| | | } |
| | | |
| | | .card-icon.personal { |
| | | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | | } |
| | | |
| | | .card-icon.group { |
| | | background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); |
| | | } |
| | | |
| | | .card-icon.department { |
| | | background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); |
| | | } |
| | | |
| | | .card-icon.company { |
| | | background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); |
| | | } |
| | | |
| | | .card-info { |
| | | flex: 1; |
| | | } |
| | | |
| | | .card-title { |
| | | font-size: 14px; |
| | | color: #666; |
| | | margin-bottom: 5px; |
| | | } |
| | | |
| | | .card-number { |
| | | font-size: 24px; |
| | | font-weight: bold; |
| | | color: #333; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .card-progress { |
| | | width: 100%; |
| | | } |
| | | |
| | | .plan-content { |
| | | background: white; |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | font-weight: bold; |
| | | color: #333; |
| | | } |
| | | |
| | | .header-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .plan-list { |
| | | padding: 20px 0; |
| | | } |
| | | |
| | | .plan-item { |
| | | border: 1px solid #e4e7ed; |
| | | border-radius: 8px; |
| | | margin-bottom: 15px; |
| | | padding: 20px; |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .plan-item:hover { |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
| | | transform: translateY(-2px); |
| | | } |
| | | |
| | | .plan-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .plan-title { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .title-text { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | color: #333; |
| | | } |
| | | |
| | | .plan-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .plan-content { |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .plan-description { |
| | | color: #666; |
| | | margin-bottom: 15px; |
| | | line-height: 1.6; |
| | | } |
| | | |
| | | .plan-meta { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 20px; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .meta-item { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 5px; |
| | | color: #666; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .plan-progress { |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .plan-tags { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 5px; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | |
| | | /* 响应式设计 */ |
| | | @media (max-width: 768px) { |
| | | .header-actions { |
| | | flex-direction: column; |
| | | gap: 15px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | background: white; |
| | | padding: 20px; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | |
| | | .left-actions { |
| | | flex-wrap: wrap; |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .right-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .plan-meta { |
| | | flex-direction: column; |
| | | |
| | | .overview-cards { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .overview-card { |
| | | height: 120px; |
| | | } |
| | | |
| | | .card-content { |
| | | display: flex; |
| | | align-items: center; |
| | | height: 100%; |
| | | } |
| | | |
| | | .card-icon { |
| | | width: 60px; |
| | | height: 60px; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin-right: 15px; |
| | | font-size: 24px; |
| | | color: white; |
| | | } |
| | | |
| | | .card-icon.personal { |
| | | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | | } |
| | | |
| | | .card-icon.group { |
| | | background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); |
| | | } |
| | | |
| | | .card-icon.department { |
| | | background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); |
| | | } |
| | | |
| | | .card-icon.company { |
| | | background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); |
| | | } |
| | | |
| | | .card-info { |
| | | flex: 1; |
| | | } |
| | | |
| | | .card-title { |
| | | font-size: 14px; |
| | | color: #666; |
| | | margin-bottom: 5px; |
| | | } |
| | | |
| | | .card-number { |
| | | font-size: 24px; |
| | | font-weight: bold; |
| | | color: #333; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .card-progress { |
| | | width: 100%; |
| | | } |
| | | |
| | | .plan-content { |
| | | background: white; |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | font-weight: bold; |
| | | color: #333; |
| | | } |
| | | |
| | | .header-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | |
| | | |
| | | .plan-list { |
| | | padding: 20px 0; |
| | | } |
| | | |
| | | .plan-item { |
| | | border: 1px solid #e4e7ed; |
| | | border-radius: 8px; |
| | | margin-bottom: 15px; |
| | | padding: 20px; |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .plan-item:hover { |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
| | | transform: translateY(-2px); |
| | | } |
| | | |
| | | .plan-header { |
| | | flex-direction: column; |
| | | align-items: flex-start; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .plan-title { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | } |
| | | } |
| | | |
| | | .title-text { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | color: #333; |
| | | } |
| | | |
| | | .plan-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .plan-content { |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .plan-description { |
| | | color: #666; |
| | | margin-bottom: 15px; |
| | | line-height: 1.6; |
| | | } |
| | | |
| | | .plan-meta { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 20px; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .meta-item { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 5px; |
| | | color: #666; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .plan-progress { |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .plan-tags { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 5px; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | |
| | | /* 响应式设计 */ |
| | | @media (max-width: 768px) { |
| | | .header-actions { |
| | | flex-direction: column; |
| | | gap: 15px; |
| | | } |
| | | |
| | | .left-actions { |
| | | flex-wrap: wrap; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .plan-meta { |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .plan-header { |
| | | flex-direction: column; |
| | | align-items: flex-start; |
| | | gap: 10px; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">程序名:</span> |
| | | <el-input |
| | | v-model="searchForm.programName" |
| | | style="width: 240px" |
| | | placeholder="请输入程序名搜索" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | /> |
| | | <el-input v-model="searchForm.programName" |
| | | style="width: 240px" |
| | | placeholder="请输入程序名搜索" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" /> |
| | | <span class="search_title ml10">执行状态:</span> |
| | | <el-select v-model="searchForm.status" clearable @change="handleQuery" style="width: 240px"> |
| | | <el-option label="运行中" :value="'running'" /> |
| | | <el-option label="已停止" :value="'stopped'" /> |
| | | <el-option label="异常" :value="'error'" /> |
| | | <el-select v-model="searchForm.status" |
| | | clearable |
| | | @change="handleQuery" |
| | | style="width: 240px"> |
| | | <el-option label="运行中" |
| | | :value="'running'" /> |
| | | <el-option label="已停止" |
| | | :value="'stopped'" /> |
| | | <el-option label="异常" |
| | | :value="'error'" /> |
| | | </el-select> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px"> |
| | | <el-button type="primary" |
| | | @click="handleQuery" |
| | | style="margin-left: 10px"> |
| | | 搜索 |
| | | </el-button> |
| | | </div> |
| | | <div> |
| | | <el-button @click="handleExport" style="margin-right: 10px">导出</el-button> |
| | | <el-button type="primary" @click="openForm('add')">新增</el-button> |
| | | <el-button type="danger" plain @click="handleDelete">删除</el-button> |
| | | <el-button @click="handleExport" |
| | | style="margin-right: 10px">导出</el-button> |
| | | <el-button type="primary" |
| | | @click="openForm('add')">新增</el-button> |
| | | <el-button type="danger" |
| | | plain |
| | | @click="handleDelete">删除</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total" |
| | | ></PIMTable> |
| | | <PIMTable rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total"></PIMTable> |
| | | </div> |
| | | |
| | | <!-- RPA表单弹窗 --> |
| | | <el-dialog |
| | | v-model="dialogVisible" |
| | | :title="dialogTitle" |
| | | width="500px" |
| | | :close-on-click-modal="false" |
| | | > |
| | | <el-form |
| | | ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="100px" |
| | | > |
| | | <el-form-item label="程序名" prop="programName"> |
| | | <el-input |
| | | v-model="form.programName" |
| | | placeholder="请输入程序名" |
| | | clearable |
| | | /> |
| | | <el-dialog v-model="dialogVisible" |
| | | :title="dialogTitle" |
| | | width="500px" |
| | | :close-on-click-modal="false"> |
| | | <el-form ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="100px"> |
| | | <el-form-item label="程序名" |
| | | prop="programName"> |
| | | <el-input v-model="form.programName" |
| | | placeholder="请输入程序名" |
| | | clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="执行状态" prop="status"> |
| | | <el-select v-model="form.status" placeholder="请选择执行状态" style="width: 100%"> |
| | | <el-option label="运行中" value="running" /> |
| | | <el-option label="已停止" value="stopped" /> |
| | | <el-option label="异常" value="error" /> |
| | | <el-form-item label="执行状态" |
| | | prop="status"> |
| | | <el-select v-model="form.status" |
| | | placeholder="请选择执行状态" |
| | | style="width: 100%"> |
| | | <el-option label="运行中" |
| | | value="running" /> |
| | | <el-option label="已停止" |
| | | value="stopped" /> |
| | | <el-option label="异常" |
| | | value="error" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="描述" prop="description"> |
| | | <el-input |
| | | v-model="form.description" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请输入RPA程序描述" |
| | | clearable |
| | | /> |
| | | <el-form-item label="描述" |
| | | prop="description"> |
| | | <el-input v-model="form.description" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请输入RPA程序描述" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="submitForm">确定</el-button> |
| | | <el-button @click="dialogVisible = false">取消</el-button> |
| | | <el-button type="primary" @click="submitForm">确定</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { onMounted, ref, reactive, toRefs, getCurrentInstance } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | import {listRpa, addRpa, updateRpa, delRpa, delRpaBatch} from "@/api/collaborativeApproval/rpaManagement.js"; |
| | | // 响应式数据 |
| | | const data = reactive({ |
| | | searchForm: { |
| | | programName: "", |
| | | status: "", |
| | | }, |
| | | form: { |
| | | programName: "", |
| | | status: "", |
| | | description: "" |
| | | }, |
| | | dialogVisible: false, |
| | | dialogTitle: "", |
| | | dialogType: "add", |
| | | tableLoading: false, |
| | | page: { |
| | | current: 1, |
| | | size: 20, |
| | | total: 0, |
| | | }, |
| | | tableData: [], |
| | | }); |
| | | |
| | | const { searchForm, form, dialogVisible, dialogTitle, dialogType, selectedIds, tableLoading, page, tableData } = toRefs(data); |
| | | |
| | | // 表单引用 |
| | | const formRef = ref(); |
| | | // 选择的行数据 |
| | | const selectedRows = ref([]); |
| | | |
| | | // 表单验证规则 |
| | | const rules = { |
| | | programName: [ |
| | | { required: true, message: "请输入程序名", trigger: "blur" } |
| | | ], |
| | | status: [ |
| | | { required: true, message: "请选择执行状态", trigger: "change" } |
| | | ] |
| | | }; |
| | | |
| | | // 表格列配置 |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "程序名", |
| | | prop: "programName", |
| | | // width: 200, |
| | | }, |
| | | { |
| | | label: "执行状态", |
| | | prop: "status", |
| | | dataType: "tag", |
| | | // width: 120, |
| | | formatData: (params) => { |
| | | const statusMap = { |
| | | running: "运行中", |
| | | stopped: "已停止", |
| | | error: "异常" |
| | | }; |
| | | return statusMap[params] || params; |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { onMounted, ref, reactive, toRefs, getCurrentInstance } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | import { |
| | | listRpa, |
| | | addRpa, |
| | | updateRpa, |
| | | delRpa, |
| | | delRpaBatch, |
| | | } from "@/api/collaborativeApproval/rpaManagement.js"; |
| | | // 响应式数据 |
| | | const data = reactive({ |
| | | searchForm: { |
| | | programName: "", |
| | | status: "", |
| | | }, |
| | | formatType: (params) => { |
| | | const typeMap = { |
| | | running: "success", |
| | | stopped: "info", |
| | | error: "danger" |
| | | }; |
| | | return typeMap[params] || "info"; |
| | | } |
| | | }, |
| | | { |
| | | label: "描述", |
| | | prop: "description", |
| | | // width: 300, |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "创建时间", |
| | | prop: "createTime", |
| | | // width: 180, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "操作", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 150, |
| | | operation: [ |
| | | { |
| | | name: "编辑", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | } |
| | | form: { |
| | | programName: "", |
| | | status: "", |
| | | description: "", |
| | | }, |
| | | dialogVisible: false, |
| | | dialogTitle: "", |
| | | dialogType: "add", |
| | | tableLoading: false, |
| | | page: { |
| | | current: 1, |
| | | size: 20, |
| | | total: 0, |
| | | }, |
| | | tableData: [], |
| | | }); |
| | | |
| | | const { |
| | | searchForm, |
| | | form, |
| | | dialogVisible, |
| | | dialogTitle, |
| | | dialogType, |
| | | selectedIds, |
| | | tableLoading, |
| | | page, |
| | | tableData, |
| | | } = toRefs(data); |
| | | |
| | | // 表单引用 |
| | | const formRef = ref(); |
| | | // 选择的行数据 |
| | | const selectedRows = ref([]); |
| | | |
| | | // 表单验证规则 |
| | | const rules = { |
| | | programName: [{ required: true, message: "请输入程序名", trigger: "blur" }], |
| | | status: [{ required: true, message: "请选择执行状态", trigger: "change" }], |
| | | }; |
| | | |
| | | // 表格列配置 |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "程序名", |
| | | prop: "programName", |
| | | // width: 200, |
| | | }, |
| | | { |
| | | label: "执行状态", |
| | | prop: "status", |
| | | dataType: "tag", |
| | | // width: 120, |
| | | formatData: params => { |
| | | const statusMap = { |
| | | running: "运行中", |
| | | stopped: "已停止", |
| | | error: "异常", |
| | | }; |
| | | return statusMap[params] || params; |
| | | }, |
| | | // { |
| | | // name: "开始", |
| | | // type: "text", |
| | | // clickFun: (row) => { |
| | | // handleStart(row); |
| | | // }, |
| | | // disabled: (row) => row.status !== 'stopped' |
| | | // }, |
| | | // { |
| | | // name: "停止", |
| | | // type: "text", |
| | | // clickFun: (row) => { |
| | | // handleStop(row); |
| | | // }, |
| | | // disabled: (row) => row.status === 'stopped' |
| | | // } |
| | | ] |
| | | } |
| | | ]); |
| | | |
| | | |
| | | // 生命周期 |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | |
| | | // 查询数据 |
| | | const handleQuery = () => { |
| | | // page.value.current = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | listRpa({...page.value, ...searchForm.value}) |
| | | .then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records |
| | | page.total = res.data.total; |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | }) |
| | | }; |
| | | |
| | | // 分页处理 |
| | | const pagination = (obj) => { |
| | | page.value.current = obj.page; |
| | | page.value.size = obj.limit; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | // 选择变化处理 |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | // 打开表单 |
| | | const openForm = (type, row) => { |
| | | dialogType.value = type; |
| | | dialogVisible.value = true; |
| | | |
| | | if (type === "add") { |
| | | dialogTitle.value = "添加RPA"; |
| | | } else { |
| | | dialogTitle.value = "编辑RPA"; |
| | | form.value = { ...row }; |
| | | } |
| | | }; |
| | | |
| | | // 提交表单 |
| | | const submitForm = async () => { |
| | | if (!formRef.value) return; |
| | | |
| | | try { |
| | | await formRef.value.validate(); |
| | | |
| | | if (dialogType.value === "add") { |
| | | // 添加新RPA |
| | | addRpa({...form.value}).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("添加成功"); |
| | | form.value = { |
| | | programName: "", |
| | | status: "", |
| | | description: "" |
| | | formatType: params => { |
| | | const typeMap = { |
| | | running: "success", |
| | | stopped: "info", |
| | | error: "danger", |
| | | }; |
| | | return typeMap[params] || "info"; |
| | | }, |
| | | }, |
| | | { |
| | | label: "描述", |
| | | prop: "description", |
| | | // width: 300, |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "创建时间", |
| | | prop: "createTime", |
| | | // width: 180, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "操作", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 150, |
| | | operation: [ |
| | | { |
| | | name: "编辑", |
| | | type: "text", |
| | | clickFun: row => { |
| | | openForm("edit", row); |
| | | }, |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }, |
| | | // { |
| | | // name: "开始", |
| | | // type: "text", |
| | | // clickFun: (row) => { |
| | | // handleStart(row); |
| | | // }, |
| | | // disabled: (row) => row.status !== 'stopped' |
| | | // }, |
| | | // { |
| | | // name: "停止", |
| | | // type: "text", |
| | | // clickFun: (row) => { |
| | | // handleStop(row); |
| | | // }, |
| | | // disabled: (row) => row.status === 'stopped' |
| | | // } |
| | | ], |
| | | }, |
| | | ]); |
| | | |
| | | // 生命周期 |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | |
| | | // 查询数据 |
| | | const handleQuery = () => { |
| | | // page.value.current = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | listRpa({ ...page.value, ...searchForm.value }) |
| | | .then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | page.total = res.data.total; |
| | | }) |
| | | .catch(err => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | // 分页处理 |
| | | const pagination = obj => { |
| | | page.value.current = obj.page; |
| | | page.value.size = obj.limit; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | // 选择变化处理 |
| | | const handleSelectionChange = selection => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | // 打开表单 |
| | | const openForm = (type, row) => { |
| | | dialogType.value = type; |
| | | dialogVisible.value = true; |
| | | |
| | | if (type === "add") { |
| | | dialogTitle.value = "添加RPA"; |
| | | } else { |
| | | // 编辑RPA |
| | | updateRpa({...form.value}).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("更新成功"); |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | dialogTitle.value = "编辑RPA"; |
| | | form.value = { ...row }; |
| | | } |
| | | } catch (error) { |
| | | console.error("表单验证失败:", error); |
| | | } |
| | | }; |
| | | }; |
| | | |
| | | // 开始RPA |
| | | const handleStart = (row) => { |
| | | ElMessageBox.confirm(`确定要启动RPA程序"${row.programName}"吗?`, "提示", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }).then(() => { |
| | | row.status = "running"; |
| | | ElMessage.success("RPA启动成功"); |
| | | getList(); |
| | | }).catch(() => { |
| | | // 用户取消 |
| | | }); |
| | | }; |
| | | // 提交表单 |
| | | const submitForm = async () => { |
| | | if (!formRef.value) return; |
| | | |
| | | // 停止RPA |
| | | const handleStop = (row) => { |
| | | ElMessageBox.confirm(`确定要停止RPA程序"${row.programName}"吗?`, "提示", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }).then(() => { |
| | | row.status = "stopped"; |
| | | ElMessage.success("RPA停止成功"); |
| | | getList(); |
| | | }).catch(() => { |
| | | // 用户取消 |
| | | }); |
| | | }; |
| | | try { |
| | | await formRef.value.validate(); |
| | | |
| | | // 删除RPA |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | ids = selectedRows.value.map((item) => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("请选择数据"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | delRpa(ids).then((res) => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("删除成功"); |
| | | getList(); |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已取消"); |
| | | }); |
| | | }; |
| | | if (dialogType.value === "add") { |
| | | // 添加新RPA |
| | | addRpa({ ...form.value }) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("添加成功"); |
| | | (form.value = { |
| | | programName: "", |
| | | status: "", |
| | | description: "", |
| | | }), |
| | | (dialogVisible.value = false); |
| | | getList(); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | } else { |
| | | // 编辑RPA |
| | | updateRpa({ ...form.value }) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("更新成功"); |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | } |
| | | } catch (error) { |
| | | console.error("表单验证失败:", error); |
| | | } |
| | | }; |
| | | |
| | | // 导出功能 |
| | | const { proxy } = getCurrentInstance() |
| | | const handleExport = () => { |
| | | proxy.download('/rpaProcessAutomation/export', { ...searchForm.value }, 'RPA管理.xlsx') |
| | | } |
| | | // 开始RPA |
| | | const handleStart = row => { |
| | | ElMessageBox.confirm(`确定要启动RPA程序"${row.programName}"吗?`, "提示", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | row.status = "running"; |
| | | ElMessage.success("RPA启动成功"); |
| | | getList(); |
| | | }) |
| | | .catch(() => { |
| | | // 用户取消 |
| | | }); |
| | | }; |
| | | |
| | | // 停止RPA |
| | | const handleStop = row => { |
| | | ElMessageBox.confirm(`确定要停止RPA程序"${row.programName}"吗?`, "提示", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | row.status = "stopped"; |
| | | ElMessage.success("RPA停止成功"); |
| | | getList(); |
| | | }) |
| | | .catch(() => { |
| | | // 用户取消 |
| | | }); |
| | | }; |
| | | |
| | | // 删除RPA |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | ids = selectedRows.value.map(item => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("请选择数据"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | delRpa(ids) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("删除成功"); |
| | | getList(); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已取消"); |
| | | }); |
| | | }; |
| | | |
| | | // 导出功能 |
| | | const { proxy } = getCurrentInstance(); |
| | | const handleExport = () => { |
| | | proxy.download( |
| | | "/rpaProcessAutomation/export", |
| | | { ...searchForm.value }, |
| | | "RPA管理.xlsx" |
| | | ); |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped></style> |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | |
| | | <!-- 规章制度管理--> |
| | | <el-card class="box-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>规章制度发布</span> |
| | | </div> |
| | | <!-- 规章制度管理--> |
| | | <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="6"> |
| | | <el-input v-model="regulationSearchForm.title" |
| | | placeholder="请输入制度标题" |
| | | clearable /> |
| | | </el-col> |
| | | <span class="search_title">制度分类:</span> |
| | | <el-col :span="4"> |
| | | <el-select v-model="regulationSearchForm.category" |
| | | placeholder="制度分类" |
| | | clearable> |
| | | <el-option label="人事制度" |
| | | value="hr" /> |
| | | <el-option label="财务制度" |
| | | value="finance" /> |
| | | <el-option label="安全制度" |
| | | value="safety" /> |
| | | <el-option label="技术制度" |
| | | value="tech" /> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-button type="primary" |
| | | @click="searchRegulations">搜索</el-button> |
| | | <el-button @click="resetRegulationSearch">重置</el-button> |
| | | <el-button @click="handleExport">导出</el-button> |
| | | <el-button type="success" |
| | | @click="handleAdd"> |
| | | 发布制度 |
| | | </el-button> |
| | | </el-col> |
| | | </el-row> |
| | | <el-table :data="regulations" |
| | | border |
| | | v-loading="tableLoading" |
| | | style="width: 100%"> |
| | | <el-table-column prop="regulationNum" |
| | | label="制度编号" |
| | | width="120" /> |
| | | <el-table-column prop="title" |
| | | label="制度标题" |
| | | min-width="150" /> |
| | | <el-table-column prop="category" |
| | | label="分类" |
| | | width="120"> |
| | | <template #default="scope"> |
| | | <el-tag>{{ getCategoryText(scope.row.category) }}</el-tag> |
| | | </template> |
| | | <div class="tab-content"> |
| | | <el-row :gutter="20" class="mb-20"> |
| | | <span class="ml-10">制度标题:</span> |
| | | <el-col :span="6"> |
| | | <el-input v-model="regulationSearchForm.title" placeholder="请输入制度标题" clearable /> |
| | | </el-col> |
| | | <span class="search_title">制度分类:</span> |
| | | <el-col :span="4"> |
| | | <el-select v-model="regulationSearchForm.category" placeholder="制度分类" clearable> |
| | | <el-option label="人事制度" value="hr" /> |
| | | <el-option label="财务制度" value="finance" /> |
| | | <el-option label="安全制度" value="safety" /> |
| | | <el-option label="技术制度" value="tech" /> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-button type="primary" @click="searchRegulations">搜索</el-button> |
| | | <el-button @click="resetRegulationSearch">重置</el-button> |
| | | <el-button @click="handleExport">导出</el-button> |
| | | <el-button type="success" @click="handleAdd"> |
| | | 发布制度 |
| | | </el-button> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-table :data="regulations" border v-loading="tableLoading" style="width: 100%"> |
| | | <el-table-column prop="regulationNum" label="制度编号" width="120" /> |
| | | <el-table-column prop="title" label="制度标题" min-width="150" /> |
| | | <el-table-column prop="category" label="分类" width="120"> |
| | | <template #default="scope"> |
| | | <el-tag>{{ getCategoryText(scope.row.category) }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="version" label="版本" width="120" /> |
| | | <el-table-column prop="createUserName" label="发布人" width="120" /> |
| | | <el-table-column prop="createTime" label="发布时间" width="180" /> |
| | | <el-table-column prop="status" label="状态" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'"> |
| | | {{ scope.row.status === 'active' ? '生效中' : '已废止' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="readCount" label="已读人数" width="100" /> |
| | | <el-table-column label="操作" width="250" 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> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </el-card> |
| | | |
| | | </el-table-column> |
| | | <el-table-column prop="version" |
| | | label="版本" |
| | | width="120" /> |
| | | <el-table-column prop="createUserName" |
| | | label="发布人" |
| | | width="120" /> |
| | | <el-table-column prop="createTime" |
| | | label="发布时间" |
| | | width="180" /> |
| | | <el-table-column prop="status" |
| | | label="状态" |
| | | width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'"> |
| | | {{ scope.row.status === 'active' ? '生效中' : '已废止' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="readCount" |
| | | label="已读人数" |
| | | width="100" /> |
| | | <el-table-column label="操作" |
| | | width="250" |
| | | 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> |
| | | </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"> |
| | |
| | | </span> |
| | | </template> |
| | | </el-dialog> --> |
| | | |
| | | <!-- 规章制度发布对话框 --> |
| | | <el-dialog v-model="showRegulationDialog" :title="operationType === 'add' ? '发布制度' : '编辑制度'" width="800px"> |
| | | <el-form :model="regulationForm" :rules="regulationRules" ref="regulationFormRef" label-width="100px"> |
| | | <el-form-item label="制度编号" prop="regulationNum"> |
| | | <el-input v-model="regulationForm.regulationNum" placeholder="请输入制度编号" /> |
| | | <el-dialog v-model="showRegulationDialog" |
| | | :title="operationType === 'add' ? '发布制度' : '编辑制度'" |
| | | width="800px"> |
| | | <el-form :model="regulationForm" |
| | | :rules="regulationRules" |
| | | ref="regulationFormRef" |
| | | label-width="100px"> |
| | | <el-form-item label="制度编号" |
| | | prop="regulationNum"> |
| | | <el-input v-model="regulationForm.regulationNum" |
| | | placeholder="请输入制度编号" /> |
| | | </el-form-item> |
| | | <el-form-item label="制度标题" prop="title"> |
| | | <el-input v-model="regulationForm.title" placeholder="请输入制度标题" /> |
| | | <el-form-item label="制度标题" |
| | | prop="title"> |
| | | <el-input v-model="regulationForm.title" |
| | | placeholder="请输入制度标题" /> |
| | | </el-form-item> |
| | | <el-form-item label="制度分类" prop="category"> |
| | | <el-select v-model="regulationForm.category" placeholder="请选择制度分类" style="width: 100%"> |
| | | <el-option label="人事制度" value="hr" /> |
| | | <el-option label="财务制度" value="finance" /> |
| | | <el-option label="安全制度" value="safety" /> |
| | | <el-option label="技术制度" value="tech" /> |
| | | <el-form-item label="制度分类" |
| | | prop="category"> |
| | | <el-select v-model="regulationForm.category" |
| | | placeholder="请选择制度分类" |
| | | style="width: 100%"> |
| | | <el-option label="人事制度" |
| | | value="hr" /> |
| | | <el-option label="财务制度" |
| | | value="finance" /> |
| | | <el-option label="安全制度" |
| | | value="safety" /> |
| | | <el-option label="技术制度" |
| | | value="tech" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="制度内容" prop="content"> |
| | | <el-input v-model="regulationForm.content" type="textarea" :rows="10" placeholder="请输入制度详细内容" /> |
| | | <el-form-item label="制度内容" |
| | | prop="content"> |
| | | <el-input v-model="regulationForm.content" |
| | | type="textarea" |
| | | :rows="10" |
| | | placeholder="请输入制度详细内容" /> |
| | | </el-form-item> |
| | | <el-form-item label="制度版本" prop="version"> |
| | | <el-input v-model="regulationForm.version" placeholder="请输入制度版本" /> |
| | | <el-form-item label="制度版本" |
| | | prop="version"> |
| | | <el-input v-model="regulationForm.version" |
| | | placeholder="请输入制度版本" /> |
| | | </el-form-item> |
| | | <el-form-item label="生效时间" prop="effectiveTime"> |
| | | <el-date-picker v-model="regulationForm.effectiveTime" type="datetime" format="YYYY-MM-DD HH:mm:ss" |
| | | value-format="YYYY-MM-DD HH:mm:ss" placeholder="选择生效时间" style="width: 100%" /> |
| | | <el-form-item label="生效时间" |
| | | prop="effectiveTime"> |
| | | <el-date-picker v-model="regulationForm.effectiveTime" |
| | | type="datetime" |
| | | format="YYYY-MM-DD HH:mm:ss" |
| | | value-format="YYYY-MM-DD HH:mm:ss" |
| | | placeholder="选择生效时间" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="适用范围" prop="scope"> |
| | | <el-form-item label="适用范围" |
| | | prop="scope"> |
| | | <el-checkbox-group v-model="regulationForm.scope"> |
| | | <el-checkbox label="all">全体员工</el-checkbox> |
| | | <el-checkbox label="manager">管理层</el-checkbox> |
| | |
| | | <el-checkbox label="tech">技术部门</el-checkbox> |
| | | </el-checkbox-group> |
| | | </el-form-item> |
| | | <el-form-item label="是否需要确认" prop="requireConfirm"> |
| | | <el-form-item label="是否需要确认" |
| | | prop="requireConfirm"> |
| | | <el-switch v-model="regulationForm.requireConfirm" /> |
| | | <span class="ml-10">开启后员工需要阅读确认</span> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="submitRegulation">发布制度</el-button> |
| | | <el-button @click="showRegulationDialog = false">取消</el-button> |
| | | <el-button type="primary" @click="submitRegulation">发布制度</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 用印详情对话框 --> |
| | | <!-- <el-dialog v-model="showSealDetailDialog" title="用印申请详情" width="700px"> |
| | | <div v-if="currentSealDetail" class="mb10"> |
| | |
| | | </el-descriptions> |
| | | </div> |
| | | </el-dialog> --> |
| | | |
| | | <!-- 规章制度详情对话框 --> |
| | | <el-dialog v-model="showRegulationDetailDialog" title="规章制度详情" width="800px"> |
| | | <el-dialog v-model="showRegulationDetailDialog" |
| | | title="规章制度详情" |
| | | width="800px"> |
| | | <div v-if="currentRegulationDetail"> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions :column="2" |
| | | border> |
| | | <el-descriptions-item label="制度编号">{{ currentRegulationDetail.id }}</el-descriptions-item> |
| | | <el-descriptions-item label="制度标题">{{ currentRegulationDetail.title }}</el-descriptions-item> |
| | | <el-descriptions-item label="分类">{{ getCategoryText(currentRegulationDetail.category) }}</el-descriptions-item> |
| | |
| | | <div class="regulation-content">{{ currentRegulationDetail.content }}</div> |
| | | </div> |
| | | <!-- 如果tableData>0 显示 --> |
| | | <div style="margin: 10px 0;" v-if="tableData && tableData.length > 0" > |
| | | <el-button type="success" @click="resetForm(currentRegulationDetail)">确认查看</el-button> |
| | | <div style="margin: 10px 0;" |
| | | v-if="tableData && tableData.length > 0"> |
| | | <el-button type="success" |
| | | @click="resetForm(currentRegulationDetail)">确认查看</el-button> |
| | | </div> |
| | | </div> |
| | | </el-dialog> |
| | | |
| | | <!-- 版本历史对话框 --> |
| | | <el-dialog v-model="showVersionHistoryDialog" title="版本历史" width="800px"> |
| | | <el-table :data="versionHistory" style="width: 100%;margin-bottom: 10px"> |
| | | <el-table-column prop="version" label="版本号" width="100" /> |
| | | <el-table-column prop="updateTime" label="更新时间" width="180" /> |
| | | <el-table-column prop="createUserName" label="更新人" width="120" /> |
| | | <el-table-column prop="changeLog" label="变更说明"> |
| | | <el-dialog v-model="showVersionHistoryDialog" |
| | | title="版本历史" |
| | | width="800px"> |
| | | <el-table :data="versionHistory" |
| | | style="width: 100%;margin-bottom: 10px"> |
| | | <el-table-column prop="version" |
| | | label="版本号" |
| | | width="100" /> |
| | | <el-table-column prop="updateTime" |
| | | label="更新时间" |
| | | width="180" /> |
| | | <el-table-column prop="createUserName" |
| | | label="更新人" |
| | | width="120" /> |
| | | <el-table-column prop="changeLog" |
| | | label="变更说明"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'"> |
| | | {{ scope.row.status === 'active' ? '生效中' : '已废止' }} |
| | |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-dialog> |
| | | |
| | | <!-- 阅读状态对话框 --> |
| | | <el-dialog v-model="showReadStatusDialog" title="阅读状态" width="800px"> |
| | | <el-table :data="readStatusList" style="width: 100%;margin-bottom: 10px"> |
| | | <el-table-column prop="employee" label="员工姓名" width="120" /> |
| | | <el-table-column prop="department" label="所属部门" width="150" /> |
| | | <el-table-column prop="createTime" label="阅读时间" width="180" /> |
| | | <el-table-column prop="confirmTime" label="确认时间" width="180" /> |
| | | <el-table-column prop="status" label="状态" width="100"> |
| | | <el-dialog v-model="showReadStatusDialog" |
| | | title="阅读状态" |
| | | width="800px"> |
| | | <el-table :data="readStatusList" |
| | | style="width: 100%;margin-bottom: 10px"> |
| | | <el-table-column prop="employee" |
| | | label="员工姓名" |
| | | width="120" /> |
| | | <el-table-column prop="department" |
| | | label="所属部门" |
| | | width="150" /> |
| | | <el-table-column prop="createTime" |
| | | label="阅读时间" |
| | | width="180" /> |
| | | <el-table-column prop="confirmTime" |
| | | label="确认时间" |
| | | width="180" /> |
| | | <el-table-column prop="status" |
| | | label="状态" |
| | | width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'confirmed' ? 'success' : 'warning'"> |
| | | {{ scope.row.status === 'confirmed' ? '已确认' : '未确认' }} |
| | |
| | | </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 { 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"; |
| | | |
| | | // 响应式数据 |
| | | 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 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 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 showRegulationDialog = ref(false) |
| | | const showRegulationDetailDialog = ref(false) |
| | | const showVersionHistoryDialog = ref(false) |
| | | const showReadStatusDialog = ref(false) |
| | | const currentRegulationDetail = ref(null) |
| | | const regulationFormRef = ref() |
| | | const regulationForm = reactive({ |
| | | id: '', |
| | | regulationNum: '', |
| | | title: '', |
| | | category: '', |
| | | content: '', |
| | | version: '', |
| | | status: 'active', |
| | | readCount: 0, |
| | | effectiveTime: '', |
| | | scope: [], |
| | | requireConfirm: false |
| | | }) |
| | | const sealSearchForm = reactive({ |
| | | title: "", |
| | | status: "", |
| | | }); |
| | | // 分页参数 |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0, |
| | | }); |
| | | // 规章制度相关 |
| | | const showRegulationDialog = ref(false); |
| | | const showRegulationDetailDialog = ref(false); |
| | | const showVersionHistoryDialog = ref(false); |
| | | const showReadStatusDialog = ref(false); |
| | | const currentRegulationDetail = ref(null); |
| | | const regulationFormRef = ref(); |
| | | const regulationForm = reactive({ |
| | | id: "", |
| | | regulationNum: "", |
| | | title: "", |
| | | category: "", |
| | | content: "", |
| | | version: "", |
| | | status: "active", |
| | | readCount: 0, |
| | | effectiveTime: "", |
| | | scope: [], |
| | | requireConfirm: false, |
| | | }); |
| | | |
| | | const readStatus = ref({ |
| | | id: '', |
| | | ruleId: '', |
| | | employee: '', |
| | | department: '', |
| | | createTime: '', |
| | | confirmTime: '', |
| | | status: 'unconfirmed' |
| | | }) |
| | | const readStatus = ref({ |
| | | id: "", |
| | | ruleId: "", |
| | | employee: "", |
| | | department: "", |
| | | createTime: "", |
| | | confirmTime: "", |
| | | status: "unconfirmed", |
| | | }); |
| | | |
| | | const regulationRules = { |
| | | title: [{ required: true, message: '请输入制度标题', trigger: 'blur' }], |
| | | category: [{ required: true, message: '请选择制度分类', trigger: 'change' }], |
| | | content: [{ required: true, message: '请输入制度内容', trigger: 'blur' }], |
| | | effectiveTime: [{ required: true, message: '请选择生效时间', trigger: 'change' }], |
| | | scope: [{ required: true, message: '请选择适用范围', trigger: 'change' }] |
| | | } |
| | | const regulationRules = { |
| | | title: [{ required: true, message: "请输入制度标题", trigger: "blur" }], |
| | | category: [{ required: true, message: "请选择制度分类", trigger: "change" }], |
| | | content: [{ required: true, message: "请输入制度内容", trigger: "blur" }], |
| | | effectiveTime: [ |
| | | { required: true, message: "请选择生效时间", trigger: "change" }, |
| | | ], |
| | | scope: [{ required: true, message: "请选择适用范围", trigger: "change" }], |
| | | }; |
| | | |
| | | const regulationSearchForm = reactive({ |
| | | title: '', |
| | | category: '' |
| | | }) |
| | | const regulationSearchForm = reactive({ |
| | | title: "", |
| | | category: "", |
| | | }); |
| | | |
| | | // 假数据 |
| | | const sealApplications = ref([]) |
| | | // 假数据 |
| | | const sealApplications = ref([]); |
| | | |
| | | const regulations = ref([]) |
| | | const regulations = ref([]); |
| | | |
| | | const versionHistory = ref([]) |
| | | const versionHistory = ref([]); |
| | | |
| | | const readStatusList = ref([]) |
| | | const readStatusList = ref([]); |
| | | // { employee: '陈志强', department: '销售部', readTime: '2025-01-11 10:30:00', confirmTime: '2025-01-11 10:35:00', status: 'confirmed' }, |
| | | // { 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 = { |
| | | hr: '人事制度', |
| | | finance: '财务制度', |
| | | safety: '安全制度', |
| | | tech: '技术制度' |
| | | } |
| | | return categoryMap[category] || '未知' |
| | | } |
| | | // 搜索印章申请 |
| | | const searchSealApplications = () => { |
| | | page.current=1 |
| | | getSealApplicationList() |
| | | // 用印申请状态 |
| | | 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 = { |
| | | hr: "人事制度", |
| | | finance: "财务制度", |
| | | safety: "安全制度", |
| | | tech: "技术制度", |
| | | }; |
| | | return categoryMap[category] || "未知"; |
| | | }; |
| | | // 搜索印章申请 |
| | | const searchSealApplications = () => { |
| | | page.current = 1; |
| | | getSealApplicationList(); |
| | | |
| | | // ElMessage.success('搜索完成') |
| | | } |
| | | // 重置印章申请搜索 |
| | | const resetSealSearch = () => { |
| | | sealSearchForm.title = '' |
| | | sealSearchForm.status = '' |
| | | searchSealApplications() |
| | | } |
| | | // 搜索制度 |
| | | const searchRegulations = () => { |
| | | page.current=1 |
| | | getRegulationList() |
| | | } |
| | | // 重置制度搜索 |
| | | const resetRegulationSearch = () => { |
| | | 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' |
| | | // ElMessage.success('搜索完成') |
| | | }; |
| | | // 重置印章申请搜索 |
| | | const resetSealSearch = () => { |
| | | sealSearchForm.title = ""; |
| | | sealSearchForm.status = ""; |
| | | searchSealApplications(); |
| | | }; |
| | | // 搜索制度 |
| | | const searchRegulations = () => { |
| | | page.current = 1; |
| | | getRegulationList(); |
| | | }; |
| | | // 重置制度搜索 |
| | | const resetRegulationSearch = () => { |
| | | 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 = () => { |
| | | operationType.value = "add"; |
| | | resetRegulationForm(); |
| | | showRegulationDialog.value = true; |
| | | }; |
| | | |
| | | // 编辑 |
| | | const handleEdit = row => { |
| | | operationType.value = "edit"; |
| | | Object.assign(regulationForm, row); |
| | | showRegulationDialog.value = true; |
| | | }; |
| | | // 废弃 |
| | | const repealEdit = row => { |
| | | operationType.value = "edit"; |
| | | Object.assign(regulationForm, row); |
| | | regulationForm.status = "repealed"; |
| | | ElMessageBox.confirm("确认废弃该制度?", "提示", { |
| | | confirmButtonText: "确定", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | updateRuleManagement(regulationForm).then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("制度废弃成功"); |
| | | // showRegulationDialog.value = false |
| | | getRegulationList(); |
| | | resetRegulationForm(); |
| | | } |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | ElMessage({ |
| | | type: "info", |
| | | message: "已取消废弃", |
| | | }); |
| | | }); |
| | | }; |
| | | // 发布制度 |
| | | const submitRegulation = async () => { |
| | | try { |
| | | await regulationFormRef.value.validate(); |
| | | if (operationType.value == "add") { |
| | | addRuleManagement(regulationForm).then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("制度发布成功"); |
| | | showRegulationDialog.value = false; |
| | | getRegulationList(); |
| | | resetRegulationForm(); |
| | | } |
| | | }); |
| | | } else { |
| | | updateRuleManagement(regulationForm).then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("制度编辑成功"); |
| | | showRegulationDialog.value = false; |
| | | resetRegulationForm(); |
| | | getRegulationList(); |
| | | } |
| | | }); |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg) |
| | | }) |
| | | |
| | | } catch (error) { |
| | | ElMessage.error('请完善申请信息') |
| | | } |
| | | } |
| | | // 新增 |
| | | const handleAdd = () => { |
| | | operationType.value = 'add' |
| | | resetRegulationForm() |
| | | showRegulationDialog.value = true |
| | | } |
| | | } catch (err) { |
| | | ElMessage.error(err.msg); |
| | | } |
| | | }; |
| | | //重置制度表单 |
| | | const resetRegulationForm = () => { |
| | | Object.assign(regulationForm, { |
| | | id: "", |
| | | regulationNum: "", |
| | | title: "", |
| | | category: "", |
| | | content: "", |
| | | version: "", |
| | | status: "active", |
| | | readCount: 0, |
| | | effectiveTime: "", |
| | | scope: [], |
| | | requireConfirm: false, |
| | | }); |
| | | }; |
| | | |
| | | // 编辑 |
| | | const handleEdit = (row) => { |
| | | operationType.value = 'edit' |
| | | Object.assign(regulationForm, row) |
| | | showRegulationDialog.value = true |
| | | } |
| | | // 废弃 |
| | | const repealEdit = (row) => { |
| | | operationType.value = 'edit' |
| | | Object.assign(regulationForm, row) |
| | | regulationForm.status = 'repealed' |
| | | ElMessageBox.confirm('确认废弃该制度?', '提示', { |
| | | confirmButtonText: '确定', |
| | | cancelButtonText: '取消', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | updateRuleManagement(regulationForm).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success('制度废弃成功') |
| | | // showRegulationDialog.value = false |
| | | getRegulationList() |
| | | resetRegulationForm() |
| | | // 查看用印申请详情 |
| | | 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; |
| | | } |
| | | }) |
| | | }).catch(() => { |
| | | ElMessage({ |
| | | type: 'info', |
| | | message: '已取消废弃' |
| | | }) |
| | | }) |
| | | } |
| | | // 发布制度 |
| | | const submitRegulation = async () => { |
| | | try { |
| | | await regulationFormRef.value.validate() |
| | | if(operationType.value == 'add'){ |
| | | addRuleManagement(regulationForm).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success('制度发布成功') |
| | | showRegulationDialog.value = false |
| | | getRegulationList() |
| | | resetRegulationForm() |
| | | }); |
| | | 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("当前用户未加入任何部门"); |
| | | } |
| | | }) |
| | | }else{ |
| | | updateRuleManagement(regulationForm).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success('制度编辑成功') |
| | | showRegulationDialog.value = false |
| | | resetRegulationForm() |
| | | getRegulationList() |
| | | }})} |
| | | }catch(err){ |
| | | ElMessage.error(err.msg) |
| | | } |
| | | } |
| | | //重置制度表单 |
| | | const resetRegulationForm = () => { |
| | | Object.assign(regulationForm, { |
| | | id: '', |
| | | regulationNum: '', |
| | | title: '', |
| | | category: '', |
| | | content: '', |
| | | version: '', |
| | | status: 'active', |
| | | readCount: 0, |
| | | effectiveTime: '', |
| | | scope: [], |
| | | requireConfirm: false |
| | | }) |
| | | } |
| | | .catch(err => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | |
| | | // 查看用印申请详情 |
| | | 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 viewVersionHistory = row => { |
| | | showVersionHistoryDialog.value = true; |
| | | const params = { |
| | | category: row.category, |
| | | }; |
| | | listRuleManagement(page, params).then(res => { |
| | | if (res.code == 200) { |
| | | versionHistory.value = res.data.records; |
| | | } |
| | | }) |
| | | }) |
| | | } |
| | | // 拒绝用印申请 |
| | | 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 |
| | | const params = { |
| | | |
| | | category: row.category |
| | | } |
| | | listRuleManagement(page,params).then(res => { |
| | | if(res.code == 200){ |
| | | versionHistory.value = res.data.records |
| | | } |
| | | }) |
| | | } |
| | | // 查看制度详情 |
| | | const viewRegulation = (row) => { |
| | | getList() |
| | | currentRegulationDetail.value = row |
| | | showRegulationDetailDialog.value = true |
| | | getReadingStatusByRuleId(row.id).then(res => { |
| | | if(res.code == 200){ |
| | | readStatusList.value = res.data |
| | | if(readStatusList.value.length==0 && tableData.value.length>0){ |
| | | }); |
| | | }; |
| | | // 查看制度详情 |
| | | const viewRegulation = row => { |
| | | getList(); |
| | | currentRegulationDetail.value = row; |
| | | showRegulationDetailDialog.value = true; |
| | | getReadingStatusByRuleId(row.id).then(res => { |
| | | if (res.code == 200) { |
| | | readStatusList.value = res.data; |
| | | if (readStatusList.value.length == 0 && tableData.value.length > 0) { |
| | | const params = { |
| | | ruleId: row.id, |
| | | employee: tableData.value[0].staffName, |
| | | department: tableData.value[0].postJob, |
| | | status: 'unconfirmed' |
| | | ruleId: row.id, |
| | | employee: tableData.value[0].staffName, |
| | | department: tableData.value[0].postJob, |
| | | status: "unconfirmed", |
| | | }; |
| | | addReadingStatus(params).then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("制度阅读成功"); |
| | | } |
| | | }); |
| | | } |
| | | addReadingStatus(params).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success('制度阅读成功') |
| | | } |
| | | }) |
| | | } |
| | | } |
| | | }) |
| | | |
| | | } |
| | | // 查看制度阅读状态 |
| | | const viewReadStatus = (row) => { |
| | | showReadStatusDialog.value = true |
| | | //查看阅读状态列表 |
| | | getReadingStatusByRuleId(row.id).then(res => { |
| | | if(res.code == 200){ |
| | | readStatusList.value = res.data |
| | | } |
| | | }) |
| | | } |
| | | |
| | | //确认查看 |
| | | const resetForm = (row) => { |
| | | console.log("row",row) |
| | | row.readCount = row.readCount + 1 |
| | | |
| | | updateRuleManagement(row).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success('查看数量修改成功') |
| | | //修改阅读状态 |
| | | //根据制度id和当前登录的员工得到阅读状态 |
| | | // let item = readStatusList.value.filter(item => item.employee == tableData.value[0].staffName ) |
| | | // if(item.length>0){ |
| | | // item[0].status = 'confirmed', |
| | | // item[0].confirmTime = new Date().toISOString().replace('T', ' ').split('.')[0]; |
| | | // } |
| | | // 筛选当前员工对应该制度的阅读状态记录 |
| | | let statusItem = readStatusList.value.find(item => item.employee === tableData.value[0].staffName && item.ruleId === row.id); |
| | | |
| | | if (statusItem) { |
| | | // 如果找到记录,更新状态和确认时间 |
| | | statusItem.status = 'confirmed'; |
| | | // 格式化时间为"YYYY-MM-DD HH:mm:ss"格式 |
| | | const now = new Date(); |
| | | statusItem.confirmTime = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`; |
| | | // statusItem.confirmTime = new Date().toISOString().replace('T', ' ').split('.')[0]; |
| | | |
| | | updateReadingStatus(statusItem).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success('制度阅读状态修改成功') |
| | | } |
| | | }) |
| | | }); |
| | | }; |
| | | // 查看制度阅读状态 |
| | | const viewReadStatus = row => { |
| | | showReadStatusDialog.value = true; |
| | | //查看阅读状态列表 |
| | | getReadingStatusByRuleId(row.id).then(res => { |
| | | if (res.code == 200) { |
| | | readStatusList.value = res.data; |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | } |
| | | }) |
| | | } |
| | | //确认查看 |
| | | const resetForm = row => { |
| | | console.log("row", row); |
| | | row.readCount = row.readCount + 1; |
| | | |
| | | // 导出规章制度 |
| | | const { proxy } = getCurrentInstance() |
| | | const handleExport = () => { |
| | | proxy.download('/rulesRegulationsManagement/export', { ...regulationSearchForm }, '规章制度.xlsx') |
| | | } |
| | | updateRuleManagement(row).then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("查看数量修改成功"); |
| | | //修改阅读状态 |
| | | //根据制度id和当前登录的员工得到阅读状态 |
| | | // let item = readStatusList.value.filter(item => item.employee == tableData.value[0].staffName ) |
| | | // if(item.length>0){ |
| | | // item[0].status = 'confirmed', |
| | | // item[0].confirmTime = new Date().toISOString().replace('T', ' ').split('.')[0]; |
| | | // } |
| | | // 筛选当前员工对应该制度的阅读状态记录 |
| | | let statusItem = readStatusList.value.find( |
| | | item => |
| | | item.employee === tableData.value[0].staffName && |
| | | item.ruleId === row.id |
| | | ); |
| | | |
| | | // 获取印章申请列表数据 |
| | | 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; |
| | | if (statusItem) { |
| | | // 如果找到记录,更新状态和确认时间 |
| | | statusItem.status = "confirmed"; |
| | | // 格式化时间为"YYYY-MM-DD HH:mm:ss"格式 |
| | | const now = new Date(); |
| | | statusItem.confirmTime = `${now.getFullYear()}-${String( |
| | | now.getMonth() + 1 |
| | | ).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")} ${String( |
| | | now.getHours() |
| | | ).padStart(2, "0")}:${String(now.getMinutes()).padStart( |
| | | 2, |
| | | "0" |
| | | )}:${String(now.getSeconds()).padStart(2, "0")}`; |
| | | // statusItem.confirmTime = new Date().toISOString().replace('T', ' ').split('.')[0]; |
| | | |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | }) |
| | | } |
| | | // 获取规章制度列表数据 |
| | | const getRegulationList = async () => { |
| | | tableLoading.value = true |
| | | listRuleManagement(page,regulationSearchForm) |
| | | .then(res => { |
| | | updateReadingStatus(statusItem).then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("制度阅读状态修改成功"); |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | regulations.value = res.data.records |
| | | // 过滤掉已废弃的制度 |
| | | // regulations.value = res.data.records.filter(item => item.status !== 'repealed') |
| | | page.value.total = res.data.total; |
| | | tableLoading.value = false; |
| | | // 导出规章制度 |
| | | const { proxy } = getCurrentInstance(); |
| | | const handleExport = () => { |
| | | proxy.download( |
| | | "/rulesRegulationsManagement/export", |
| | | { ...regulationSearchForm }, |
| | | "规章制度.xlsx" |
| | | ); |
| | | }; |
| | | |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | }) |
| | | } |
| | | // 获取印章申请列表数据 |
| | | 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 getRegulationList = async () => { |
| | | tableLoading.value = true; |
| | | listRuleManagement(page, regulationSearchForm) |
| | | .then(res => { |
| | | regulations.value = res.data.records; |
| | | // 过滤掉已废弃的制度 |
| | | // regulations.value = res.data.records.filter(item => item.status !== 'repealed') |
| | | page.value.total = res.data.total; |
| | | tableLoading.value = false; |
| | | }) |
| | | .catch(err => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | // 初始化 |
| | | getSealApplicationList() |
| | | getRegulationList() |
| | | }) |
| | | onMounted(() => { |
| | | // 初始化 |
| | | getSealApplicationList(); |
| | | getRegulationList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .tab-content { |
| | | padding: 20px 0; |
| | | } |
| | | .tab-content { |
| | | padding: 20px 0; |
| | | } |
| | | |
| | | .mb-20 { |
| | | margin-bottom: 20px; |
| | | } |
| | | .mb-20 { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .mt-20 { |
| | | margin-top: 20px; |
| | | } |
| | | .mt-20 { |
| | | margin-top: 20px; |
| | | } |
| | | |
| | | .ml-10 { |
| | | margin-left: 10px; |
| | | } |
| | | .ml-10 { |
| | | margin-left: 10px; |
| | | } |
| | | |
| | | .regulation-content { |
| | | background-color: #f5f5f5; |
| | | padding: 15px; |
| | | border-radius: 4px; |
| | | line-height: 1.6; |
| | | white-space: pre-wrap; |
| | | height: 200px; |
| | | } |
| | | .regulation-content { |
| | | background-color: #f5f5f5; |
| | | padding: 15px; |
| | | border-radius: 4px; |
| | | line-height: 1.6; |
| | | white-space: pre-wrap; |
| | | height: 200px; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | </style> |
| | |
| | | <span>用印管理发布</span> |
| | | </div> |
| | | </template> |
| | | |
| | | |
| | | <!-- 用印申请管理 --> |
| | | <div class="tab-content"> |
| | | <el-row :gutter="20" class="mb-20 "> |
| | | <span class="ml-10">用印标题:</span> |
| | | <el-col :span="6"> |
| | | <el-input v-model="sealSearchForm.title" placeholder="请输入申请标题" clearable /> |
| | | </el-col> |
| | | <span class="search_title">审批状态:</span> |
| | | <el-col :span="6"> |
| | | <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="8"> |
| | | <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> |
| | | |
| | | <el-table :data="sealApplications" border v-loading="tableLoading" style="width: 100%"> |
| | | <el-table-column prop="applicationNum" label="申请编号" width="120" /> |
| | | <el-table-column prop="title" label="申请标题" min-width="200" /> |
| | | <el-table-column prop="createUserName" label="申请人" width="120" /> |
| | | <el-table-column prop="department" label="所属部门" width="150" /> |
| | | <el-table-column prop="sealType" label="用印类型" width="120"> |
| | | <template #default="scope"> |
| | | {{ getSealTypeText(scope.row.sealType) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="createTime" label="申请时间" width="180" /> |
| | | <el-table-column prop="status" label="状态" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getStatusType(scope.row.status)"> |
| | | {{ getStatusText(scope.row.status) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" width="200" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button link @click="viewSealDetail(scope.row)">查看</el-button> |
| | | <el-button |
| | | v-if="scope.row.status === 'pending'" |
| | | link |
| | | type="primary" |
| | | @click="approveSeal(scope.row)" |
| | | > |
| | | 审批 |
| | | </el-button> |
| | | <el-button |
| | | v-if="scope.row.status === 'pending'" |
| | | link |
| | | type="danger" |
| | | @click="rejectSeal(scope.row)" |
| | | > |
| | | 拒绝 |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <pagination v-show="total > 0" :total="total" layout="total, sizes, prev, pager, next, jumper" |
| | | :page="page.current" :limit="page.size" @pagination="paginationChange" /> |
| | | </div> |
| | | <!-- 用印申请管理 --> |
| | | <div class="tab-content"> |
| | | <el-row :gutter="20" |
| | | class="mb-20 "> |
| | | <span class="ml-10">用印标题:</span> |
| | | <el-col :span="6"> |
| | | <el-input v-model="sealSearchForm.title" |
| | | placeholder="请输入申请标题" |
| | | clearable /> |
| | | </el-col> |
| | | <span class="search_title">审批状态:</span> |
| | | <el-col :span="6"> |
| | | <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="8"> |
| | | <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> |
| | | <el-table :data="sealApplications" |
| | | border |
| | | v-loading="tableLoading" |
| | | style="width: 100%"> |
| | | <el-table-column prop="applicationNum" |
| | | label="申请编号" |
| | | width="120" /> |
| | | <el-table-column prop="title" |
| | | label="申请标题" |
| | | min-width="200" /> |
| | | <el-table-column prop="createUserName" |
| | | label="申请人" |
| | | width="120" /> |
| | | <el-table-column prop="department" |
| | | label="所属部门" |
| | | width="150" /> |
| | | <el-table-column prop="sealType" |
| | | label="用印类型" |
| | | width="120"> |
| | | <template #default="scope"> |
| | | {{ getSealTypeText(scope.row.sealType) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="createTime" |
| | | label="申请时间" |
| | | width="180" /> |
| | | <el-table-column prop="status" |
| | | label="状态" |
| | | width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getStatusType(scope.row.status)"> |
| | | {{ getStatusText(scope.row.status) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" |
| | | width="200" |
| | | fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button link |
| | | @click="viewSealDetail(scope.row)">查看</el-button> |
| | | <el-button v-if="scope.row.status === 'pending'" |
| | | link |
| | | type="primary" |
| | | @click="approveSeal(scope.row)"> |
| | | 审批 |
| | | </el-button> |
| | | <el-button v-if="scope.row.status === 'pending'" |
| | | link |
| | | type="danger" |
| | | @click="rejectSeal(scope.row)"> |
| | | 拒绝 |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <pagination v-show="total > 0" |
| | | :total="total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="page.current" |
| | | :limit="page.size" |
| | | @pagination="paginationChange" /> |
| | | </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-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 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-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 label="申请原因" |
| | | prop="reason"> |
| | | <el-input v-model="sealForm.reason" |
| | | type="textarea" |
| | | :rows="4" |
| | | placeholder="请详细说明用印原因" /> |
| | | </el-form-item> |
| | | <el-form-item label="紧急程度" prop="urgency"> |
| | | <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-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="submitSealApplication">提交申请</el-button> |
| | | <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"> |
| | | <el-form :model="regulationForm" :rules="regulationRules" ref="regulationFormRef" label-width="100px"> |
| | |
| | | </span> |
| | | </template> |
| | | </el-dialog> --> |
| | | |
| | | <!-- 用印详情对话框 --> |
| | | <el-dialog v-model="showSealDetailDialog" title="用印申请详情" width="700px"> |
| | | <div v-if="currentSealDetail" class="mb10"> |
| | | <el-descriptions :column="2" border> |
| | | <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> |
| | |
| | | {{ getStatusText(currentSealDetail.status) }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="申请原因" :span="2">{{ currentSealDetail.reason }}</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"> |
| | | <el-dialog v-model="showRegulationDetailDialog" |
| | | title="规章制度详情" |
| | | width="800px"> |
| | | <div v-if="currentRegulationDetail"> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions :column="2" |
| | | border> |
| | | <el-descriptions-item label="制度编号">{{ currentRegulationDetail.id }}</el-descriptions-item> |
| | | <el-descriptions-item label="制度标题">{{ currentRegulationDetail.title }}</el-descriptions-item> |
| | | <el-descriptions-item label="分类">{{ getCategoryText(currentRegulationDetail.category) }}</el-descriptions-item> |
| | |
| | | <div class="regulation-content">{{ currentRegulationDetail.content }}</div> |
| | | </div> |
| | | <!-- 如果tableData>0 显示 --> |
| | | <div style="margin: 10px 0;" v-if="tableData && tableData.length > 0" > |
| | | <el-button type="success" @click="resetForm(currentRegulationDetail)">确认查看</el-button> |
| | | <div style="margin: 10px 0;" |
| | | v-if="tableData && tableData.length > 0"> |
| | | <el-button type="success" |
| | | @click="resetForm(currentRegulationDetail)">确认查看</el-button> |
| | | </div> |
| | | </div> |
| | | </el-dialog> |
| | | |
| | | <!-- 版本历史对话框 --> |
| | | <el-dialog v-model="showVersionHistoryDialog" title="版本历史" width="800px"> |
| | | <el-table :data="versionHistory" style="width: 100%;margin-bottom: 10px"> |
| | | <el-table-column prop="version" label="版本号" width="100" /> |
| | | <el-table-column prop="updateTime" label="更新时间" width="180" /> |
| | | <el-table-column prop="createUserName" label="更新人" width="120" /> |
| | | <el-table-column prop="changeLog" label="变更说明"> |
| | | <el-dialog v-model="showVersionHistoryDialog" |
| | | title="版本历史" |
| | | width="800px"> |
| | | <el-table :data="versionHistory" |
| | | style="width: 100%;margin-bottom: 10px"> |
| | | <el-table-column prop="version" |
| | | label="版本号" |
| | | width="100" /> |
| | | <el-table-column prop="updateTime" |
| | | label="更新时间" |
| | | width="180" /> |
| | | <el-table-column prop="createUserName" |
| | | label="更新人" |
| | | width="120" /> |
| | | <el-table-column prop="changeLog" |
| | | label="变更说明"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'"> |
| | | {{ scope.row.status === 'active' ? '生效中' : '已废止' }} |
| | |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-dialog> |
| | | |
| | | <!-- 阅读状态对话框 --> |
| | | <el-dialog v-model="showReadStatusDialog" title="阅读状态" width="800px"> |
| | | <el-table :data="readStatusList" style="width: 100%;margin-bottom: 10px"> |
| | | <el-table-column prop="employee" label="员工姓名" width="120" /> |
| | | <el-table-column prop="department" label="所属部门" width="150" /> |
| | | <el-table-column prop="createTime" label="阅读时间" width="180" /> |
| | | <el-table-column prop="confirmTime" label="确认时间" width="180" /> |
| | | <el-table-column prop="status" label="状态" width="100"> |
| | | <el-dialog v-model="showReadStatusDialog" |
| | | title="阅读状态" |
| | | width="800px"> |
| | | <el-table :data="readStatusList" |
| | | style="width: 100%;margin-bottom: 10px"> |
| | | <el-table-column prop="employee" |
| | | label="员工姓名" |
| | | width="120" /> |
| | | <el-table-column prop="department" |
| | | label="所属部门" |
| | | width="150" /> |
| | | <el-table-column prop="createTime" |
| | | label="阅读时间" |
| | | width="180" /> |
| | | <el-table-column prop="confirmTime" |
| | | label="确认时间" |
| | | width="180" /> |
| | | <el-table-column prop="status" |
| | | label="状态" |
| | | width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'confirmed' ? 'success' : 'warning'"> |
| | | {{ scope.row.status === 'confirmed' ? '已确认' : '未确认' }} |
| | |
| | | </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 { 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"; |
| | | |
| | | // 响应式数据 |
| | | 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 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 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: 100, |
| | | total: 0 |
| | | }) |
| | | // 规章制度相关 |
| | | const showRegulationDialog = ref(false) |
| | | const showRegulationDetailDialog = ref(false) |
| | | const showVersionHistoryDialog = ref(false) |
| | | const showReadStatusDialog = ref(false) |
| | | const currentRegulationDetail = ref(null) |
| | | const regulationFormRef = ref() |
| | | const regulationForm = reactive({ |
| | | id: '', |
| | | regulationNum: '', |
| | | title: '', |
| | | category: '', |
| | | content: '', |
| | | version: '', |
| | | status: 'active', |
| | | readCount: 0, |
| | | effectiveTime: '', |
| | | scope: [], |
| | | requireConfirm: false |
| | | }) |
| | | const sealSearchForm = reactive({ |
| | | title: "", |
| | | status: "", |
| | | }); |
| | | // 分页参数 |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | total: 0, |
| | | }); |
| | | // 规章制度相关 |
| | | const showRegulationDialog = ref(false); |
| | | const showRegulationDetailDialog = ref(false); |
| | | const showVersionHistoryDialog = ref(false); |
| | | const showReadStatusDialog = ref(false); |
| | | const currentRegulationDetail = ref(null); |
| | | const regulationFormRef = ref(); |
| | | const regulationForm = reactive({ |
| | | id: "", |
| | | regulationNum: "", |
| | | title: "", |
| | | category: "", |
| | | content: "", |
| | | version: "", |
| | | status: "active", |
| | | readCount: 0, |
| | | effectiveTime: "", |
| | | scope: [], |
| | | requireConfirm: false, |
| | | }); |
| | | |
| | | const readStatus = ref({ |
| | | id: '', |
| | | ruleId: '', |
| | | employee: '', |
| | | department: '', |
| | | createTime: '', |
| | | confirmTime: '', |
| | | status: 'unconfirmed' |
| | | }) |
| | | const readStatus = ref({ |
| | | id: "", |
| | | ruleId: "", |
| | | employee: "", |
| | | department: "", |
| | | createTime: "", |
| | | confirmTime: "", |
| | | status: "unconfirmed", |
| | | }); |
| | | |
| | | const regulationRules = { |
| | | title: [{ required: true, message: '请输入制度标题', trigger: 'blur' }], |
| | | category: [{ required: true, message: '请选择制度分类', trigger: 'change' }], |
| | | content: [{ required: true, message: '请输入制度内容', trigger: 'blur' }], |
| | | effectiveTime: [{ required: true, message: '请选择生效时间', trigger: 'change' }], |
| | | scope: [{ required: true, message: '请选择适用范围', trigger: 'change' }] |
| | | } |
| | | const regulationRules = { |
| | | title: [{ required: true, message: "请输入制度标题", trigger: "blur" }], |
| | | category: [{ required: true, message: "请选择制度分类", trigger: "change" }], |
| | | content: [{ required: true, message: "请输入制度内容", trigger: "blur" }], |
| | | effectiveTime: [ |
| | | { required: true, message: "请选择生效时间", trigger: "change" }, |
| | | ], |
| | | scope: [{ required: true, message: "请选择适用范围", trigger: "change" }], |
| | | }; |
| | | |
| | | const regulationSearchForm = reactive({ |
| | | title: '', |
| | | category: '' |
| | | }) |
| | | const regulationSearchForm = reactive({ |
| | | title: "", |
| | | category: "", |
| | | }); |
| | | |
| | | // 假数据 |
| | | const sealApplications = ref([]) |
| | | // 假数据 |
| | | const sealApplications = ref([]); |
| | | |
| | | const regulations = ref([]) |
| | | const regulations = ref([]); |
| | | |
| | | const versionHistory = ref([]) |
| | | const versionHistory = ref([]); |
| | | |
| | | const readStatusList = ref([]) |
| | | const readStatusList = ref([]); |
| | | // { employee: '陈志强', department: '销售部', readTime: '2025-01-11 10:30:00', confirmTime: '2025-01-11 10:35:00', status: 'confirmed' }, |
| | | // { 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 = { |
| | | hr: '人事制度', |
| | | finance: '财务制度', |
| | | safety: '安全制度', |
| | | tech: '技术制度' |
| | | } |
| | | return categoryMap[category] || '未知' |
| | | } |
| | | // 搜索印章申请 |
| | | const searchSealApplications = () => { |
| | | page.current=1 |
| | | getSealApplicationList() |
| | | // 用印申请状态 |
| | | 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 = { |
| | | hr: "人事制度", |
| | | finance: "财务制度", |
| | | safety: "安全制度", |
| | | tech: "技术制度", |
| | | }; |
| | | return categoryMap[category] || "未知"; |
| | | }; |
| | | // 搜索印章申请 |
| | | const searchSealApplications = () => { |
| | | page.current = 1; |
| | | getSealApplicationList(); |
| | | |
| | | // ElMessage.success('搜索完成') |
| | | } |
| | | // 重置印章申请搜索 |
| | | const resetSealSearch = () => { |
| | | sealSearchForm.title = '' |
| | | sealSearchForm.status = '' |
| | | searchSealApplications() |
| | | } |
| | | // 搜索制度 |
| | | const searchRegulations = () => { |
| | | page.current=1 |
| | | getRegulationList() |
| | | } |
| | | // 重置制度搜索 |
| | | const resetRegulationSearch = () => { |
| | | 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' |
| | | // ElMessage.success('搜索完成') |
| | | }; |
| | | // 重置印章申请搜索 |
| | | const resetSealSearch = () => { |
| | | sealSearchForm.title = ""; |
| | | sealSearchForm.status = ""; |
| | | searchSealApplications(); |
| | | }; |
| | | // 搜索制度 |
| | | const searchRegulations = () => { |
| | | page.current = 1; |
| | | getRegulationList(); |
| | | }; |
| | | // 重置制度搜索 |
| | | const resetRegulationSearch = () => { |
| | | 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 = () => { |
| | | operationType.value = "add"; |
| | | resetRegulationForm(); |
| | | showRegulationDialog.value = true; |
| | | }; |
| | | |
| | | // 编辑 |
| | | const handleEdit = row => { |
| | | operationType.value = "edit"; |
| | | Object.assign(regulationForm, row); |
| | | showRegulationDialog.value = true; |
| | | }; |
| | | // 废弃 |
| | | const repealEdit = row => { |
| | | operationType.value = "edit"; |
| | | Object.assign(regulationForm, row); |
| | | regulationForm.status = "repealed"; |
| | | ElMessageBox.confirm("确认废弃该制度?", "提示", { |
| | | confirmButtonText: "确定", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | updateRuleManagement(regulationForm).then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("制度废弃成功"); |
| | | // showRegulationDialog.value = false |
| | | getRegulationList(); |
| | | resetRegulationForm(); |
| | | } |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | ElMessage({ |
| | | type: "info", |
| | | message: "已取消废弃", |
| | | }); |
| | | }); |
| | | }; |
| | | // 发布制度 |
| | | const submitRegulation = async () => { |
| | | try { |
| | | await regulationFormRef.value.validate(); |
| | | if (operationType.value == "add") { |
| | | addRuleManagement(regulationForm).then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("制度发布成功"); |
| | | showRegulationDialog.value = false; |
| | | getRegulationList(); |
| | | resetRegulationForm(); |
| | | } |
| | | }); |
| | | } else { |
| | | updateRuleManagement(regulationForm).then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("制度编辑成功"); |
| | | showRegulationDialog.value = false; |
| | | resetRegulationForm(); |
| | | getRegulationList(); |
| | | } |
| | | }); |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg) |
| | | }) |
| | | |
| | | } catch (error) { |
| | | ElMessage.error('请完善申请信息') |
| | | } |
| | | } |
| | | // 新增 |
| | | const handleAdd = () => { |
| | | operationType.value = 'add' |
| | | resetRegulationForm() |
| | | showRegulationDialog.value = true |
| | | } |
| | | } catch (err) { |
| | | ElMessage.error(err.msg); |
| | | } |
| | | }; |
| | | //重置制度表单 |
| | | const resetRegulationForm = () => { |
| | | Object.assign(regulationForm, { |
| | | id: "", |
| | | regulationNum: "", |
| | | title: "", |
| | | category: "", |
| | | content: "", |
| | | version: "", |
| | | status: "active", |
| | | readCount: 0, |
| | | effectiveTime: "", |
| | | scope: [], |
| | | requireConfirm: false, |
| | | }); |
| | | }; |
| | | |
| | | // 编辑 |
| | | const handleEdit = (row) => { |
| | | operationType.value = 'edit' |
| | | Object.assign(regulationForm, row) |
| | | showRegulationDialog.value = true |
| | | } |
| | | // 废弃 |
| | | const repealEdit = (row) => { |
| | | operationType.value = 'edit' |
| | | Object.assign(regulationForm, row) |
| | | regulationForm.status = 'repealed' |
| | | ElMessageBox.confirm('确认废弃该制度?', '提示', { |
| | | confirmButtonText: '确定', |
| | | cancelButtonText: '取消', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | updateRuleManagement(regulationForm).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success('制度废弃成功') |
| | | // showRegulationDialog.value = false |
| | | getRegulationList() |
| | | resetRegulationForm() |
| | | // 查看用印申请详情 |
| | | 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; |
| | | } |
| | | }) |
| | | }).catch(() => { |
| | | ElMessage({ |
| | | type: 'info', |
| | | message: '已取消废弃' |
| | | }) |
| | | }) |
| | | } |
| | | // 发布制度 |
| | | const submitRegulation = async () => { |
| | | try { |
| | | await regulationFormRef.value.validate() |
| | | if(operationType.value == 'add'){ |
| | | addRuleManagement(regulationForm).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success('制度发布成功') |
| | | showRegulationDialog.value = false |
| | | getRegulationList() |
| | | resetRegulationForm() |
| | | }); |
| | | staffJoinListPage({ staffState: 1, ...page }) |
| | | .then(res => { |
| | | tableLoading.value = false; |
| | | // tableData.value = res.data.records |
| | | // //筛选出和currentUser同名的人员 |
| | | tableData.value = res.data.records.filter( |
| | | item => item.staffName === currentUser.value |
| | | ); |
| | | page.total = res.data.total; |
| | | |
| | | if (tableData.value.length == 0) { |
| | | ElMessage.error("当前用户未加入任何部门"); |
| | | } |
| | | }) |
| | | }else{ |
| | | updateRuleManagement(regulationForm).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success('制度编辑成功') |
| | | showRegulationDialog.value = false |
| | | resetRegulationForm() |
| | | getRegulationList() |
| | | }})} |
| | | }catch(err){ |
| | | ElMessage.error(err.msg) |
| | | } |
| | | } |
| | | //重置制度表单 |
| | | const resetRegulationForm = () => { |
| | | Object.assign(regulationForm, { |
| | | id: '', |
| | | regulationNum: '', |
| | | title: '', |
| | | category: '', |
| | | content: '', |
| | | version: '', |
| | | status: 'active', |
| | | readCount: 0, |
| | | effectiveTime: '', |
| | | scope: [], |
| | | requireConfirm: false |
| | | }) |
| | | } |
| | | .catch(err => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | |
| | | // 查看用印申请详情 |
| | | 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 viewVersionHistory = row => { |
| | | showVersionHistoryDialog.value = true; |
| | | const params = { |
| | | category: row.category, |
| | | }; |
| | | listRuleManagement(page, params).then(res => { |
| | | if (res.code == 200) { |
| | | versionHistory.value = res.data.records; |
| | | } |
| | | }) |
| | | }) |
| | | } |
| | | // 拒绝用印申请 |
| | | 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, ...page}).then(res => { |
| | | tableLoading.value = false; |
| | | // tableData.value = res.data.records |
| | | // //筛选出和currentUser同名的人员 |
| | | tableData.value = res.data.records.filter(item => item.staffName === currentUser.value) |
| | | page.total = res.data.total; |
| | | |
| | | if(tableData.value.length == 0){ |
| | | ElMessage.error('当前用户未加入任何部门') |
| | | } |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | }) |
| | | |
| | | |
| | | }; |
| | | |
| | | // 查看制度版本历史 |
| | | const viewVersionHistory = (row) => { |
| | | showVersionHistoryDialog.value = true |
| | | const params = { |
| | | |
| | | category: row.category |
| | | } |
| | | listRuleManagement(page,params).then(res => { |
| | | if(res.code == 200){ |
| | | versionHistory.value = res.data.records |
| | | } |
| | | }) |
| | | } |
| | | // 查看制度详情 |
| | | const viewRegulation = (row) => { |
| | | getList() |
| | | currentRegulationDetail.value = row |
| | | showRegulationDetailDialog.value = true |
| | | getReadingStatusByRuleId(row.id).then(res => { |
| | | if(res.code == 200){ |
| | | readStatusList.value = res.data |
| | | if(readStatusList.value.length==0 && tableData.value.length>0){ |
| | | }); |
| | | }; |
| | | // 查看制度详情 |
| | | const viewRegulation = row => { |
| | | getList(); |
| | | currentRegulationDetail.value = row; |
| | | showRegulationDetailDialog.value = true; |
| | | getReadingStatusByRuleId(row.id).then(res => { |
| | | if (res.code == 200) { |
| | | readStatusList.value = res.data; |
| | | if (readStatusList.value.length == 0 && tableData.value.length > 0) { |
| | | const params = { |
| | | ruleId: row.id, |
| | | employee: tableData.value[0].staffName, |
| | | department: tableData.value[0].postJob, |
| | | status: 'unconfirmed' |
| | | ruleId: row.id, |
| | | employee: tableData.value[0].staffName, |
| | | department: tableData.value[0].postJob, |
| | | status: "unconfirmed", |
| | | }; |
| | | addReadingStatus(params).then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("制度阅读成功"); |
| | | } |
| | | }); |
| | | } |
| | | addReadingStatus(params).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success('制度阅读成功') |
| | | } |
| | | }) |
| | | } |
| | | } |
| | | }) |
| | | |
| | | } |
| | | // 查看制度阅读状态 |
| | | const viewReadStatus = (row) => { |
| | | showReadStatusDialog.value = true |
| | | //查看阅读状态列表 |
| | | getReadingStatusByRuleId(row.id).then(res => { |
| | | if(res.code == 200){ |
| | | readStatusList.value = res.data |
| | | } |
| | | }) |
| | | } |
| | | |
| | | //确认查看 |
| | | const resetForm = (row) => { |
| | | console.log("row",row) |
| | | row.readCount = row.readCount + 1 |
| | | |
| | | updateRuleManagement(row).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success('查看数量修改成功') |
| | | //修改阅读状态 |
| | | //根据制度id和当前登录的员工得到阅读状态 |
| | | // let item = readStatusList.value.filter(item => item.employee == tableData.value[0].staffName ) |
| | | // if(item.length>0){ |
| | | // item[0].status = 'confirmed', |
| | | // item[0].confirmTime = new Date().toISOString().replace('T', ' ').split('.')[0]; |
| | | // } |
| | | // 筛选当前员工对应该制度的阅读状态记录 |
| | | let statusItem = readStatusList.value.find(item => item.employee === tableData.value[0].staffName && item.ruleId === row.id); |
| | | |
| | | if (statusItem) { |
| | | // 如果找到记录,更新状态和确认时间 |
| | | statusItem.status = 'confirmed'; |
| | | // 格式化时间为"YYYY-MM-DD HH:mm:ss"格式 |
| | | const now = new Date(); |
| | | statusItem.confirmTime = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`; |
| | | // statusItem.confirmTime = new Date().toISOString().replace('T', ' ').split('.')[0]; |
| | | |
| | | updateReadingStatus(statusItem).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success('制度阅读状态修改成功') |
| | | } |
| | | }) |
| | | }); |
| | | }; |
| | | // 查看制度阅读状态 |
| | | const viewReadStatus = row => { |
| | | showReadStatusDialog.value = true; |
| | | //查看阅读状态列表 |
| | | getReadingStatusByRuleId(row.id).then(res => { |
| | | if (res.code == 200) { |
| | | readStatusList.value = res.data; |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | } |
| | | }) |
| | | } |
| | | //确认查看 |
| | | const resetForm = row => { |
| | | console.log("row", row); |
| | | row.readCount = row.readCount + 1; |
| | | |
| | | // 导出用印申请 |
| | | const { proxy } = getCurrentInstance() |
| | | const handleExport = () => { |
| | | proxy.download('/sealApplicationManagement/export', { ...sealSearchForm }, '用印申请.xlsx') |
| | | } |
| | | updateRuleManagement(row).then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("查看数量修改成功"); |
| | | //修改阅读状态 |
| | | //根据制度id和当前登录的员工得到阅读状态 |
| | | // let item = readStatusList.value.filter(item => item.employee == tableData.value[0].staffName ) |
| | | // if(item.length>0){ |
| | | // item[0].status = 'confirmed', |
| | | // item[0].confirmTime = new Date().toISOString().replace('T', ' ').split('.')[0]; |
| | | // } |
| | | // 筛选当前员工对应该制度的阅读状态记录 |
| | | let statusItem = readStatusList.value.find( |
| | | item => |
| | | item.employee === tableData.value[0].staffName && |
| | | item.ruleId === row.id |
| | | ); |
| | | |
| | | // 获取印章申请列表数据 |
| | | 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.total = sealApplications.value.length |
| | | } else { |
| | | // 如果没有currentFactoryName,则显示所有数据 |
| | | sealApplications.value = res.data.records |
| | | page.total = res.data.total |
| | | } |
| | | // sealApplications.value = res.data.records |
| | | // page.value.total = res.data.total; |
| | | tableLoading.value = false; |
| | | if (statusItem) { |
| | | // 如果找到记录,更新状态和确认时间 |
| | | statusItem.status = "confirmed"; |
| | | // 格式化时间为"YYYY-MM-DD HH:mm:ss"格式 |
| | | const now = new Date(); |
| | | statusItem.confirmTime = `${now.getFullYear()}-${String( |
| | | now.getMonth() + 1 |
| | | ).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")} ${String( |
| | | now.getHours() |
| | | ).padStart(2, "0")}:${String(now.getMinutes()).padStart( |
| | | 2, |
| | | "0" |
| | | )}:${String(now.getSeconds()).padStart(2, "0")}`; |
| | | // statusItem.confirmTime = new Date().toISOString().replace('T', ' ').split('.')[0]; |
| | | |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | }) |
| | | } |
| | | // 获取规章制度列表数据 |
| | | const getRegulationList = async () => { |
| | | tableLoading.value = true |
| | | listRuleManagement(page,regulationSearchForm) |
| | | .then(res => { |
| | | updateReadingStatus(statusItem).then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("制度阅读状态修改成功"); |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | regulations.value = res.data.records |
| | | // 过滤掉已废弃的制度 |
| | | // regulations.value = res.data.records.filter(item => item.status !== 'repealed') |
| | | page.total = res.data.total; |
| | | tableLoading.value = false; |
| | | // 导出用印申请 |
| | | const { proxy } = getCurrentInstance(); |
| | | const handleExport = () => { |
| | | proxy.download( |
| | | "/sealApplicationManagement/export", |
| | | { ...sealSearchForm }, |
| | | "用印申请.xlsx" |
| | | ); |
| | | }; |
| | | |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | }) |
| | | } |
| | | // 获取印章申请列表数据 |
| | | 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.total = sealApplications.value.length; |
| | | } else { |
| | | // 如果没有currentFactoryName,则显示所有数据 |
| | | sealApplications.value = res.data.records; |
| | | page.total = res.data.total; |
| | | } |
| | | // sealApplications.value = res.data.records |
| | | // page.value.total = res.data.total; |
| | | tableLoading.value = false; |
| | | }) |
| | | .catch(err => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | // 获取规章制度列表数据 |
| | | const getRegulationList = async () => { |
| | | tableLoading.value = true; |
| | | listRuleManagement(page, regulationSearchForm) |
| | | .then(res => { |
| | | regulations.value = res.data.records; |
| | | // 过滤掉已废弃的制度 |
| | | // regulations.value = res.data.records.filter(item => item.status !== 'repealed') |
| | | page.total = res.data.total; |
| | | tableLoading.value = false; |
| | | }) |
| | | .catch(err => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | // 初始化 |
| | | getSealApplicationList() |
| | | getRegulationList() |
| | | }) |
| | | onMounted(() => { |
| | | // 初始化 |
| | | getSealApplicationList(); |
| | | getRegulationList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .tab-content { |
| | | padding: 20px 0; |
| | | } |
| | | .tab-content { |
| | | padding: 20px 0; |
| | | } |
| | | |
| | | .mb-20 { |
| | | margin-bottom: 20px; |
| | | } |
| | | .mb-20 { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .mt-20 { |
| | | margin-top: 20px; |
| | | } |
| | | .mt-20 { |
| | | margin-top: 20px; |
| | | } |
| | | |
| | | .ml-10 { |
| | | margin-left: 10px; |
| | | } |
| | | .ml-10 { |
| | | margin-left: 10px; |
| | | } |
| | | |
| | | .regulation-content { |
| | | background-color: #f5f5f5; |
| | | padding: 15px; |
| | | border-radius: 4px; |
| | | line-height: 1.6; |
| | | white-space: pre-wrap; |
| | | height: 200px; |
| | | } |
| | | .regulation-content { |
| | | background-color: #f5f5f5; |
| | | padding: 15px; |
| | | border-radius: 4px; |
| | | line-height: 1.6; |
| | | white-space: pre-wrap; |
| | | height: 200px; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | </style> |
| | |
| | | <div class="defect-management"> |
| | | <!-- 操作按钮 --> |
| | | <div class="actions"> |
| | | <el-button type="primary" @click="showRegisterDialog = true">登记缺陷</el-button> |
| | | <el-button type="primary" |
| | | @click="showRegisterDialog = true">登记缺陷</el-button> |
| | | </div> |
| | | |
| | | <!-- 缺陷列表 --> |
| | | <el-table :data="defectList" style="width: 100%; margin-top: 10px;" border> |
| | | <el-table-column prop="deviceName" label="设备名称" width="180"></el-table-column> |
| | | <el-table-column prop="defectDescription" label="缺陷描述" win-width="300"></el-table-column> |
| | | <el-table-column prop="status" label="状态" width="220"> |
| | | <el-table :data="defectList" |
| | | style="width: 100%; margin-top: 10px;" |
| | | border> |
| | | <el-table-column prop="deviceName" |
| | | label="设备名称" |
| | | width="180"></el-table-column> |
| | | <el-table-column prop="defectDescription" |
| | | label="缺陷描述" |
| | | win-width="300"></el-table-column> |
| | | <el-table-column prop="status" |
| | | label="状态" |
| | | width="220"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="row.status === '严重缺陷' ? 'danger' : 'success'"> |
| | | {{ row.status }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" width="220"> |
| | | <el-table-column label="操作" |
| | | width="220"> |
| | | <template #default="{ row }"> |
| | | <el-button |
| | | v-if="row.status === '严重缺陷' || row.status === '一般缺陷'" |
| | | type="text" |
| | | @click="eliminateDefect(row)" |
| | | > |
| | | <el-button v-if="row.status === '严重缺陷' || row.status === '一般缺陷'" |
| | | type="text" |
| | | @click="eliminateDefect(row)"> |
| | | 消除缺陷 |
| | | </el-button> |
| | | <!-- <el-button |
| | |
| | | > |
| | | 转维修单 |
| | | </el-button> --> |
| | | <el-button type="text" @click="getLedger(row.deviceLedgerId)"> |
| | | <el-button type="text" |
| | | @click="getLedger(row.deviceLedgerId)"> |
| | | 查看台账 |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- 缺陷登记对话框 --> |
| | | <el-dialog title="登记设备缺陷" v-model="showRegisterDialog" width="50%"> |
| | | <el-form :model="defectForm" :rules="defectRules" ref="defectFormRef" label-width="100px"> |
| | | <el-form-item label="设备名称" prop="deviceName"> |
| | | <el-select v-model="defectForm.deviceLedgerId" @change="setDeviceModel"> |
| | | <el-option |
| | | v-for="(item, index) in deviceOptions" |
| | | :key="index" |
| | | :label="item.deviceName" |
| | | :value="item.id" |
| | | ></el-option> |
| | | <el-dialog title="登记设备缺陷" |
| | | v-model="showRegisterDialog" |
| | | width="50%"> |
| | | <el-form :model="defectForm" |
| | | :rules="defectRules" |
| | | ref="defectFormRef" |
| | | label-width="100px"> |
| | | <el-form-item label="设备名称" |
| | | prop="deviceName"> |
| | | <el-select v-model="defectForm.deviceLedgerId" |
| | | @change="setDeviceModel"> |
| | | <el-option v-for="(item, index) in deviceOptions" |
| | | :key="index" |
| | | :label="item.deviceName" |
| | | :value="item.id"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="缺陷描述" prop="defectDescription"> |
| | | <el-input type="textarea" v-model="defectForm.defectDescription"></el-input> |
| | | <el-form-item label="缺陷描述" |
| | | prop="defectDescription"> |
| | | <el-input type="textarea" |
| | | v-model="defectForm.defectDescription"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="设备状态" prop="status"> |
| | | <el-form-item label="设备状态" |
| | | prop="status"> |
| | | <el-radio-group v-model="defectForm.status"> |
| | | <el-radio label="正常">正常</el-radio> |
| | | <el-radio label="一般缺陷">一般缺陷</el-radio> |
| | |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="submitDefectForm">确定</el-button> |
| | | <el-button @click="showRegisterDialog = false">取消</el-button> |
| | | <el-button type="primary" @click="submitDefectForm">确定</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 缺陷设备台账对话框 --> |
| | | <el-dialog title="缺陷设备台账" v-model="showLedgerDialog" width="80%"> |
| | | <el-table :data="ledgerList" style="width: 100%; margin-top: 10px;" border> |
| | | <el-table-column prop="deviceName" label="设备名称"></el-table-column> |
| | | <el-table-column prop="defectDescription" label="缺陷描述"></el-table-column> |
| | | <el-table-column prop="status" label="状态"></el-table-column> |
| | | <el-table-column prop="eliminateTime" label="消缺时间"></el-table-column> |
| | | <el-dialog title="缺陷设备台账" |
| | | v-model="showLedgerDialog" |
| | | width="80%"> |
| | | <el-table :data="ledgerList" |
| | | style="width: 100%; margin-top: 10px;" |
| | | border> |
| | | <el-table-column prop="deviceName" |
| | | label="设备名称"></el-table-column> |
| | | <el-table-column prop="defectDescription" |
| | | label="缺陷描述"></el-table-column> |
| | | <el-table-column prop="status" |
| | | label="状态"></el-table-column> |
| | | <el-table-column prop="eliminateTime" |
| | | label="消缺时间"></el-table-column> |
| | | </el-table> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive } from 'vue'; |
| | | import { ElMessage } from 'element-plus'; |
| | | import { getDeviceLedger } from "@/api/equipmentManagement/ledger"; |
| | | // 假设以下是后端接口 |
| | | import { |
| | | registerDefect, |
| | | getDefectList, |
| | | eliminateDefect as apiEliminateDefect, |
| | | getDefectLedger, |
| | | deleteDefect |
| | | } from '@/api/equipmentManagement/defectManagement'; |
| | | import { ref, reactive } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { getDeviceLedger } from "@/api/equipmentManagement/ledger"; |
| | | // 假设以下是后端接口 |
| | | import { |
| | | registerDefect, |
| | | getDefectList, |
| | | eliminateDefect as apiEliminateDefect, |
| | | getDefectLedger, |
| | | deleteDefect, |
| | | } from "@/api/equipmentManagement/defectManagement"; |
| | | |
| | | // 缺陷列表 |
| | | const defectList = ref([]); |
| | | // 登记对话框显示状态 |
| | | const showRegisterDialog = ref(false); |
| | | // 台账对话框显示状态 |
| | | const showLedgerDialog = ref(false); |
| | | // 缺陷表单 |
| | | const defectForm = reactive({ |
| | | deviceLedgerId: '', |
| | | defectDescription: '', |
| | | status: '', |
| | | }); |
| | | const deviceOptions = ref([]); |
| | | // 表单验证规则 |
| | | const defectRules = reactive({ |
| | | deviceLedgerId: [{ required: true, message: '请输入设备名称', trigger: 'blur' }], |
| | | defectDescription: [{ required: true, message: '请输入缺陷描述', trigger: 'blur' }] |
| | | }); |
| | | // 表单引用 |
| | | const defectFormRef = ref(null); |
| | | // 台账列表 |
| | | const ledgerList = ref([]); |
| | | // 缺陷列表 |
| | | const defectList = ref([]); |
| | | // 登记对话框显示状态 |
| | | const showRegisterDialog = ref(false); |
| | | // 台账对话框显示状态 |
| | | const showLedgerDialog = ref(false); |
| | | // 缺陷表单 |
| | | const defectForm = reactive({ |
| | | deviceLedgerId: "", |
| | | defectDescription: "", |
| | | status: "", |
| | | }); |
| | | const deviceOptions = ref([]); |
| | | // 表单验证规则 |
| | | const defectRules = reactive({ |
| | | deviceLedgerId: [ |
| | | { required: true, message: "请输入设备名称", trigger: "blur" }, |
| | | ], |
| | | defectDescription: [ |
| | | { required: true, message: "请输入缺陷描述", trigger: "blur" }, |
| | | ], |
| | | }); |
| | | // 表单引用 |
| | | const defectFormRef = ref(null); |
| | | // 台账列表 |
| | | const ledgerList = ref([]); |
| | | |
| | | const loadDeviceName = async () => { |
| | | const { data } = await getDeviceLedger(); |
| | | // console.log(data); |
| | | deviceOptions.value = data; |
| | | }; |
| | | const loadDeviceName = async () => { |
| | | const { data } = await getDeviceLedger(); |
| | | // console.log(data); |
| | | deviceOptions.value = data; |
| | | }; |
| | | |
| | | // 获取缺陷列表 |
| | | const fetchDefectList = async () => { |
| | | try { |
| | | const res = await getDefectList(); |
| | | if (res.code === 200) { |
| | | defectList.value = res.data.records; |
| | | } else { |
| | | ElMessage.error(res.message || '获取缺陷列表失败'); |
| | | // 获取缺陷列表 |
| | | const fetchDefectList = async () => { |
| | | try { |
| | | const res = await getDefectList(); |
| | | if (res.code === 200) { |
| | | defectList.value = res.data.records; |
| | | } else { |
| | | ElMessage.error(res.message || "获取缺陷列表失败"); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error("获取缺陷列表失败"); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error('获取缺陷列表失败'); |
| | | } |
| | | }; |
| | | }; |
| | | |
| | | // 提交缺陷登记表单 |
| | | const submitDefectForm = async () => { |
| | | if (!defectFormRef.value) return; |
| | | try { |
| | | await defectFormRef.value.validate(); |
| | | const res = await registerDefect(defectForm); |
| | | if (res.code === 200) { |
| | | ElMessage.success('缺陷登记成功'); |
| | | showRegisterDialog.value = false; |
| | | fetchDefectList(); |
| | | } else { |
| | | ElMessage.error(res.message || '缺陷登记失败'); |
| | | // 提交缺陷登记表单 |
| | | const submitDefectForm = async () => { |
| | | if (!defectFormRef.value) return; |
| | | try { |
| | | await defectFormRef.value.validate(); |
| | | const res = await registerDefect(defectForm); |
| | | if (res.code === 200) { |
| | | ElMessage.success("缺陷登记成功"); |
| | | showRegisterDialog.value = false; |
| | | fetchDefectList(); |
| | | } else { |
| | | ElMessage.error(res.message || "缺陷登记失败"); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error("请填写完整表单信息"); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error('请填写完整表单信息'); |
| | | } |
| | | }; |
| | | }; |
| | | |
| | | // 消除缺陷 |
| | | const eliminateDefect = async (row) => { |
| | | |
| | | try { |
| | | const res = await apiEliminateDefect(row); |
| | | if (res.code === 200) { |
| | | ElMessage.success('缺陷消除成功'); |
| | | fetchDefectList(); |
| | | } else { |
| | | ElMessage.error(res.message || '缺陷消除失败'); |
| | | // 消除缺陷 |
| | | const eliminateDefect = async row => { |
| | | try { |
| | | const res = await apiEliminateDefect(row); |
| | | if (res.code === 200) { |
| | | ElMessage.success("缺陷消除成功"); |
| | | fetchDefectList(); |
| | | } else { |
| | | ElMessage.error(res.message || "缺陷消除失败"); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error("缺陷消除失败"); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error('缺陷消除失败'); |
| | | } |
| | | }; |
| | | }; |
| | | |
| | | // // 转维修工单 |
| | | // const transferToRepairOrder = async (id) => { |
| | | // try { |
| | | // const res = await transferToRepair(id); |
| | | // if (res.code === 200) { |
| | | // ElMessage.success('转维修工单成功'); |
| | | // } else { |
| | | // ElMessage.error(res.message || '转维修工单失败'); |
| | | // } |
| | | // } catch (error) { |
| | | // ElMessage.error('转维修工单失败'); |
| | | // } |
| | | // }; |
| | | // // 转维修工单 |
| | | // const transferToRepairOrder = async (id) => { |
| | | // try { |
| | | // const res = await transferToRepair(id); |
| | | // if (res.code === 200) { |
| | | // ElMessage.success('转维修工单成功'); |
| | | // } else { |
| | | // ElMessage.error(res.message || '转维修工单失败'); |
| | | // } |
| | | // } catch (error) { |
| | | // ElMessage.error('转维修工单失败'); |
| | | // } |
| | | // }; |
| | | |
| | | // 获取缺陷设备台账 |
| | | const getLedger = async (deviceLedgerId) => { |
| | | try { |
| | | const res = await getDefectLedger(deviceLedgerId); |
| | | if (res.code === 200) { |
| | | ledgerList.value = res.data.records; |
| | | showLedgerDialog.value = true; |
| | | } else { |
| | | ElMessage.error(res.message || '获取缺陷设备台账失败'); |
| | | // 获取缺陷设备台账 |
| | | const getLedger = async deviceLedgerId => { |
| | | try { |
| | | const res = await getDefectLedger(deviceLedgerId); |
| | | if (res.code === 200) { |
| | | ledgerList.value = res.data.records; |
| | | showLedgerDialog.value = true; |
| | | } else { |
| | | ElMessage.error(res.message || "获取缺陷设备台账失败"); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error("获取缺陷设备台账失败"); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error('获取缺陷设备台账失败'); |
| | | } |
| | | }; |
| | | }; |
| | | |
| | | // 组件挂载时获取缺陷列表 |
| | | const onMounted = () => { |
| | | fetchDefectList(); |
| | | loadDeviceName(); |
| | | }; |
| | | onMounted(); |
| | | // 组件挂载时获取缺陷列表 |
| | | const onMounted = () => { |
| | | fetchDefectList(); |
| | | loadDeviceName(); |
| | | }; |
| | | onMounted(); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .defect-management { |
| | | padding: 20px; |
| | | } |
| | | .defect-management { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .actions { |
| | | margin-bottom: 10px; |
| | | } |
| | | .actions { |
| | | margin-bottom: 10px; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog :title="operationType === 'add' ? '新增巡检任务' : '编辑巡检任务'" |
| | | v-model="dialogVisitable" width="800px" @close="cancel"> |
| | | <el-form ref="formRef" :model="form" :rules="rules" label-width="120px"> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="设备名称" prop="taskId"> |
| | | <el-select v-model="form.taskId" @change="setDeviceModel" filterable> |
| | | <el-option |
| | | v-for="(item, index) in deviceOptions" |
| | | :key="index" |
| | | :label="item.deviceName" |
| | | :value="item.id" |
| | | ></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="巡检人" prop="inspector"> |
| | | <el-select v-model="form.inspector" filterable |
| | | default-first-option |
| | | :reserve-keyword="false" placeholder="请选择" multiple clearable> |
| | | <el-option v-for="item in userList" :label="item.nickName" :value="item.userId" :key="item.userId"/> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="备注" prop="remarks"> |
| | | <el-input v-model="form.remarks" placeholder="请输入备注" type="textarea" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="登记时间" prop="dateStr"> |
| | | <el-date-picker |
| | | v-model="form.dateStr" |
| | | type="date" |
| | | placeholder="选择登记日期" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="任务频率" prop="frequencyType"> |
| | | <el-select v-model="form.frequencyType" placeholder="请选择" clearable> |
| | | <el-option label="每日" value="DAILY"/> |
| | | <el-option label="每周" value="WEEKLY"/> |
| | | <el-option label="每月" value="MONTHLY"/> |
| | | <!-- <el-option label="季度" value="QUARTERLY"/> --> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" v-if="form.frequencyType === 'DAILY' && form.frequencyType"> |
| | | <el-form-item label="日期" prop="frequencyDetail"> |
| | | <el-time-picker v-model="form.frequencyDetail" placeholder="选择时间" format="HH:mm" |
| | | value-format="HH:mm" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" v-if="form.frequencyType === 'WEEKLY' && form.frequencyType"> |
| | | <el-form-item label="日期" prop="frequencyDetail"> |
| | | <el-select v-model="form.week" placeholder="请选择" clearable style="width: 50%"> |
| | | <el-option label="周一" value="MON"/> |
| | | <el-option label="周二" value="TUE"/> |
| | | <el-option label="周三" value="WED"/> |
| | | <el-option label="周四" value="THU"/> |
| | | <el-option label="周五" value="FRI"/> |
| | | <el-option label="周六" value="SAT"/> |
| | | <el-option label="周日" value="SUN"/> |
| | | </el-select> |
| | | <el-time-picker v-model="form.time" placeholder="选择时间" format="HH:mm" |
| | | value-format="HH:mm" style="width: 50%"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" v-if="form.frequencyType === 'MONTHLY' && form.frequencyType"> |
| | | <el-form-item label="日期" prop="frequencyDetail"> |
| | | <el-date-picker |
| | | v-model="form.frequencyDetail" |
| | | type="datetime" |
| | | clearable |
| | | placeholder="选择开始日期" |
| | | format="DD,HH:mm" |
| | | value-format="DD,HH:mm" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" v-if="form.frequencyType === 'QUARTERLY' && form.frequencyType"> |
| | | <el-form-item label="日期" prop="frequencyDetail"> |
| | | <el-date-picker |
| | | v-model="form.frequencyDetail" |
| | | type="datetime" |
| | | clearable |
| | | placeholder="选择开始日期" |
| | | format="MM,DD,HH:mm" |
| | | value-format="MM,DD,HH:mm" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="cancel">取消</el-button> |
| | | <el-button type="primary" @click="submitForm">保存</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | <div> |
| | | <el-dialog :title="operationType === 'add' ? '新增巡检任务' : '编辑巡检任务'" |
| | | v-model="dialogVisitable" |
| | | width="800px" |
| | | @close="cancel"> |
| | | <el-form ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="120px"> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="设备名称" |
| | | prop="taskId"> |
| | | <el-select v-model="form.taskId" |
| | | @change="setDeviceModel" |
| | | filterable> |
| | | <el-option v-for="(item, index) in deviceOptions" |
| | | :key="index" |
| | | :label="item.deviceName" |
| | | :value="item.id"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="巡检人" |
| | | prop="inspector"> |
| | | <el-select v-model="form.inspector" |
| | | filterable |
| | | default-first-option |
| | | :reserve-keyword="false" |
| | | placeholder="请选择" |
| | | multiple |
| | | clearable> |
| | | <el-option v-for="item in userList" |
| | | :label="item.nickName" |
| | | :value="item.userId" |
| | | :key="item.userId" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="备注" |
| | | prop="remarks"> |
| | | <el-input v-model="form.remarks" |
| | | placeholder="请输入备注" |
| | | type="textarea" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="登记时间" |
| | | prop="dateStr"> |
| | | <el-date-picker v-model="form.dateStr" |
| | | type="date" |
| | | placeholder="选择登记日期" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="任务频率" |
| | | prop="frequencyType"> |
| | | <el-select v-model="form.frequencyType" |
| | | placeholder="请选择" |
| | | clearable> |
| | | <el-option label="每日" |
| | | value="DAILY" /> |
| | | <el-option label="每周" |
| | | value="WEEKLY" /> |
| | | <el-option label="每月" |
| | | value="MONTHLY" /> |
| | | <!-- <el-option label="季度" value="QUARTERLY"/> --> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" |
| | | v-if="form.frequencyType === 'DAILY' && form.frequencyType"> |
| | | <el-form-item label="日期" |
| | | prop="frequencyDetail"> |
| | | <el-time-picker v-model="form.frequencyDetail" |
| | | placeholder="选择时间" |
| | | format="HH:mm" |
| | | value-format="HH:mm" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" |
| | | v-if="form.frequencyType === 'WEEKLY' && form.frequencyType"> |
| | | <el-form-item label="日期" |
| | | prop="frequencyDetail"> |
| | | <el-select v-model="form.week" |
| | | placeholder="请选择" |
| | | clearable |
| | | style="width: 50%"> |
| | | <el-option label="周一" |
| | | value="MON" /> |
| | | <el-option label="周二" |
| | | value="TUE" /> |
| | | <el-option label="周三" |
| | | value="WED" /> |
| | | <el-option label="周四" |
| | | value="THU" /> |
| | | <el-option label="周五" |
| | | value="FRI" /> |
| | | <el-option label="周六" |
| | | value="SAT" /> |
| | | <el-option label="周日" |
| | | value="SUN" /> |
| | | </el-select> |
| | | <el-time-picker v-model="form.time" |
| | | placeholder="选择时间" |
| | | format="HH:mm" |
| | | value-format="HH:mm" |
| | | style="width: 50%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" |
| | | v-if="form.frequencyType === 'MONTHLY' && form.frequencyType"> |
| | | <el-form-item label="日期" |
| | | prop="frequencyDetail"> |
| | | <el-date-picker v-model="form.frequencyDetail" |
| | | type="datetime" |
| | | clearable |
| | | placeholder="选择开始日期" |
| | | format="DD,HH:mm" |
| | | value-format="DD,HH:mm" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" |
| | | v-if="form.frequencyType === 'QUARTERLY' && form.frequencyType"> |
| | | <el-form-item label="日期" |
| | | prop="frequencyDetail"> |
| | | <el-date-picker v-model="form.frequencyDetail" |
| | | type="datetime" |
| | | clearable |
| | | placeholder="选择开始日期" |
| | | format="MM,DD,HH:mm" |
| | | value-format="MM,DD,HH:mm" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="submitForm">保存</el-button> |
| | | <el-button @click="cancel">取消</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {reactive, ref} from "vue"; |
| | | import useUserStore from '@/store/modules/user' |
| | | import {addOrEditTimingTask} from "@/api/inspectionManagement/index.js"; |
| | | import {userListNoPageByTenantId} from "@/api/system/user.js"; |
| | | import { getDeviceLedger } from "@/api/equipmentManagement/ledger"; |
| | | import { reactive, ref } from "vue"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { addOrEditTimingTask } from "@/api/inspectionManagement/index.js"; |
| | | import { userListNoPageByTenantId } from "@/api/system/user.js"; |
| | | import { getDeviceLedger } from "@/api/equipmentManagement/ledger"; |
| | | |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits() |
| | | const userStore = useUserStore() |
| | | const dialogVisitable = ref(false); |
| | | const operationType = ref('add'); |
| | | const deviceOptions = ref([]); |
| | | const data = reactive({ |
| | | form: { |
| | | taskId: undefined, |
| | | taskName: undefined, |
| | | inspector: '', |
| | | inspectorIds: '', |
| | | remarks: '', |
| | | frequencyType: '', |
| | | frequencyDetail: '', |
| | | week: '', |
| | | time: '', |
| | | dateStr: '' |
| | | }, |
| | | rules: { |
| | | taskId: [{ required: true, message: "请选择设备", trigger: "change" },], |
| | | inspector: [{ required: true, message: "请输入巡检人", trigger: "blur" },], |
| | | dateStr: [{ required: true, message: "请选择登记时间", trigger: "change" }] |
| | | } |
| | | }) |
| | | const { form, rules } = toRefs(data) |
| | | const userList = ref([]) |
| | | const { proxy } = getCurrentInstance(); |
| | | const emit = defineEmits(); |
| | | const userStore = useUserStore(); |
| | | const dialogVisitable = ref(false); |
| | | const operationType = ref("add"); |
| | | const deviceOptions = ref([]); |
| | | const data = reactive({ |
| | | form: { |
| | | taskId: undefined, |
| | | taskName: undefined, |
| | | inspector: "", |
| | | inspectorIds: "", |
| | | remarks: "", |
| | | frequencyType: "", |
| | | frequencyDetail: "", |
| | | week: "", |
| | | time: "", |
| | | dateStr: "", |
| | | }, |
| | | rules: { |
| | | taskId: [{ required: true, message: "请选择设备", trigger: "change" }], |
| | | inspector: [{ required: true, message: "请输入巡检人", trigger: "blur" }], |
| | | dateStr: [{ required: true, message: "请选择登记时间", trigger: "change" }], |
| | | }, |
| | | }); |
| | | const { form, rules } = toRefs(data); |
| | | const userList = ref([]); |
| | | |
| | | const loadDeviceName = async () => { |
| | | const { data } = await getDeviceLedger(); |
| | | deviceOptions.value = data; |
| | | }; |
| | | const loadDeviceName = async () => { |
| | | const { data } = await getDeviceLedger(); |
| | | deviceOptions.value = data; |
| | | }; |
| | | |
| | | const setDeviceModel = (id) => { |
| | | const option = deviceOptions.value.find((item) => item.id === id); |
| | | if (option) { |
| | | form.value.taskName = option.deviceName; |
| | | } |
| | | } |
| | | const setDeviceModel = id => { |
| | | const option = deviceOptions.value.find(item => item.id === id); |
| | | if (option) { |
| | | form.value.taskName = option.deviceName; |
| | | } |
| | | }; |
| | | |
| | | // 打开弹框 |
| | | const openDialog = async (type, row) => { |
| | | dialogVisitable.value = true |
| | | operationType.value = type |
| | | |
| | | // 重置表单 |
| | | resetForm(); |
| | | |
| | | // 加载用户列表 |
| | | userListNoPageByTenantId().then((res) => { |
| | | userList.value = res.data; |
| | | }); |
| | | |
| | | // 加载设备列表 |
| | | await loadDeviceName(); |
| | | |
| | | if (type === 'edit' && row) { |
| | | form.value = {...row} |
| | | form.value.inspector = form.value.inspectorIds.split(',').map(Number) |
| | | |
| | | // 如果有设备ID,自动设置设备信息 |
| | | if (form.value.taskId) { |
| | | setDeviceModel(form.value.taskId); |
| | | } |
| | | } |
| | | } |
| | | // 打开弹框 |
| | | const openDialog = async (type, row) => { |
| | | dialogVisitable.value = true; |
| | | operationType.value = type; |
| | | |
| | | // 关闭对话框 |
| | | const cancel = () => { |
| | | resetForm() |
| | | dialogVisitable.value = false |
| | | emit('closeDia') |
| | | } |
| | | // 重置表单 |
| | | resetForm(); |
| | | |
| | | // 重置表单函数 |
| | | const resetForm = () => { |
| | | if (proxy.$refs.formRef) { |
| | | proxy.$refs.formRef.resetFields() |
| | | } |
| | | // 重置表单数据确保设备信息正确重置 |
| | | form.value = { |
| | | taskId: undefined, |
| | | taskName: undefined, |
| | | inspector: '', |
| | | inspectorIds: '', |
| | | remarks: '', |
| | | frequencyType: '', |
| | | frequencyDetail: '', |
| | | week: '', |
| | | time: '' |
| | | } |
| | | } |
| | | // 加载用户列表 |
| | | userListNoPageByTenantId().then(res => { |
| | | userList.value = res.data; |
| | | }); |
| | | |
| | | // 提交表单 |
| | | const submitForm = () => { |
| | | proxy.$refs["formRef"].validate(async valid => { |
| | | if (valid) { |
| | | try { |
| | | form.value.inspectorIds = form.value.inspector.join(',') |
| | | delete form.value.inspector |
| | | |
| | | if (form.value.frequencyType === 'WEEKLY') { |
| | | let frequencyDetail = '' |
| | | frequencyDetail = form.value.week + ',' + form.value.time |
| | | form.value.frequencyDetail = frequencyDetail |
| | | } |
| | | |
| | | let res = await userStore.getInfo() |
| | | form.value.registrantId = res.user.userId |
| | | |
| | | await addOrEditTimingTask(form.value) |
| | | cancel() |
| | | proxy.$modal.msgSuccess('提交成功') |
| | | } catch (error) { |
| | | proxy.$modal.msgError('提交失败,请重试') |
| | | } |
| | | } |
| | | }) |
| | | } |
| | | defineExpose({ openDialog }) |
| | | // 加载设备列表 |
| | | await loadDeviceName(); |
| | | |
| | | if (type === "edit" && row) { |
| | | form.value = { ...row }; |
| | | form.value.inspector = form.value.inspectorIds.split(",").map(Number); |
| | | |
| | | // 如果有设备ID,自动设置设备信息 |
| | | if (form.value.taskId) { |
| | | setDeviceModel(form.value.taskId); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | // 关闭对话框 |
| | | const cancel = () => { |
| | | resetForm(); |
| | | dialogVisitable.value = false; |
| | | emit("closeDia"); |
| | | }; |
| | | |
| | | // 重置表单函数 |
| | | const resetForm = () => { |
| | | if (proxy.$refs.formRef) { |
| | | proxy.$refs.formRef.resetFields(); |
| | | } |
| | | // 重置表单数据确保设备信息正确重置 |
| | | form.value = { |
| | | taskId: undefined, |
| | | taskName: undefined, |
| | | inspector: "", |
| | | inspectorIds: "", |
| | | remarks: "", |
| | | frequencyType: "", |
| | | frequencyDetail: "", |
| | | week: "", |
| | | time: "", |
| | | }; |
| | | }; |
| | | |
| | | // 提交表单 |
| | | const submitForm = () => { |
| | | proxy.$refs["formRef"].validate(async valid => { |
| | | if (valid) { |
| | | try { |
| | | form.value.inspectorIds = form.value.inspector.join(","); |
| | | delete form.value.inspector; |
| | | |
| | | if (form.value.frequencyType === "WEEKLY") { |
| | | let frequencyDetail = ""; |
| | | frequencyDetail = form.value.week + "," + form.value.time; |
| | | form.value.frequencyDetail = frequencyDetail; |
| | | } |
| | | |
| | | let res = await userStore.getInfo(); |
| | | form.value.registrantId = res.user.userId; |
| | | |
| | | await addOrEditTimingTask(form.value); |
| | | cancel(); |
| | | proxy.$modal.msgSuccess("提交成功"); |
| | | } catch (error) { |
| | | proxy.$modal.msgError("提交失败,请重试"); |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | defineExpose({ openDialog }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | |
| | | </style> |
| | |
| | | <div class="spare-part-category"> |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <el-text class="mx-1" size="large">设备分类</el-text> |
| | | <el-text class="mx-1" |
| | | size="large">设备分类</el-text> |
| | | <div> |
| | | <el-button @click="fetchTreeData" :loading="loading">刷新</el-button> |
| | | <el-button type="primary" @click="addCategory" >新增</el-button> |
| | | <el-button @click="fetchTreeData" |
| | | :loading="loading">刷新</el-button> |
| | | <el-button type="primary" |
| | | @click="addCategory">新增</el-button> |
| | | </div> |
| | | </div> |
| | | <el-table |
| | | v-loading="loading" |
| | | :data="renderTableData" |
| | | style="width: 100%; margin-top: 10px;" |
| | | border |
| | | row-key="id" |
| | | :tree-props="{ children: 'children', hasChildren: 'hasChildren' }" |
| | | > |
| | | <el-table-column prop="name" label="分类名称" width="450"> |
| | | <el-table v-loading="loading" |
| | | :data="renderTableData" |
| | | style="width: 100%; margin-top: 10px;" |
| | | border |
| | | row-key="id" |
| | | :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"> |
| | | <el-table-column prop="name" |
| | | label="分类名称" |
| | | width="450"> |
| | | <template #default="{ row }"> |
| | | <span :style="{ paddingLeft: getIndentation(row) + 'px' }"> |
| | | {{ row.name }} |
| | | </span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="sparePartsNo" label="分类编号" width="200"></el-table-column> |
| | | <el-table-column prop="status" label="状态" width="100"> |
| | | <el-table-column prop="sparePartsNo" |
| | | label="分类编号" |
| | | width="200"></el-table-column> |
| | | <el-table-column prop="status" |
| | | label="状态" |
| | | width="100"> |
| | | <template #default="{ row }"> |
| | | <el-tag type="success" size="small">{{ row.status }}</el-tag> |
| | | <el-tag type="success" |
| | | size="small">{{ row.status }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="description" label="描述" win-width="330"></el-table-column> |
| | | <el-table-column label="操作" width="180" fixed="right"> |
| | | <el-table-column prop="description" |
| | | label="描述" |
| | | win-width="330"></el-table-column> |
| | | <el-table-column label="操作" |
| | | width="180" |
| | | fixed="right"> |
| | | <template #default="{ row }"> |
| | | <el-button |
| | | type="text" |
| | | size="small" |
| | | @click="() => editCategory(row)" |
| | | :disabled="loading" |
| | | > |
| | | <el-button type="text" |
| | | size="small" |
| | | @click="() => editCategory(row)" |
| | | :disabled="loading"> |
| | | 编辑 |
| | | </el-button> |
| | | <el-button |
| | | type="text" |
| | | size="small" |
| | | @click="() => deleteCategory(row.id)" |
| | | style="color: #f56c6c;" |
| | | :disabled="loading" |
| | | > |
| | | <el-button type="text" |
| | | size="small" |
| | | @click="() => deleteCategory(row.id)" |
| | | style="color: #f56c6c;" |
| | | :disabled="loading"> |
| | | 删除 |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | <el-dialog title="分类管理" v-model="dialogVisible" width="60%"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="100px"> |
| | | <el-form-item label="分类名称" prop="name"> |
| | | <el-dialog title="分类管理" |
| | | v-model="dialogVisible" |
| | | width="60%"> |
| | | <el-form :model="form" |
| | | :rules="rules" |
| | | ref="formRef" |
| | | label-width="100px"> |
| | | <el-form-item label="分类名称" |
| | | prop="name"> |
| | | <el-input v-model="form.name"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="分类编号" prop="sparePartsNo"> |
| | | <el-form-item label="分类编号" |
| | | prop="sparePartsNo"> |
| | | <el-input v-model="form.sparePartsNo"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="状态" prop="status"> |
| | | <el-select v-model="form.status" placeholder="请选择状态"> |
| | | <el-option label="正常" value="正常"></el-option> |
| | | <el-option label="禁用" value="禁用"></el-option> |
| | | <el-form-item label="状态" |
| | | prop="status"> |
| | | <el-select v-model="form.status" |
| | | placeholder="请选择状态"> |
| | | <el-option label="正常" |
| | | value="正常"></el-option> |
| | | <el-option label="禁用" |
| | | value="禁用"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="描述" prop="description"> |
| | | <el-form-item label="描述" |
| | | prop="description"> |
| | | <el-input v-model="form.description"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="上级分类" prop="parentId"> |
| | | <el-select v-model="form.parentId" placeholder="请选择上级分类"> |
| | | <el-option label="无上级分类" :value="null"></el-option> |
| | | <el-option |
| | | v-for="(item, index) in categories" |
| | | :key="index" |
| | | :label="item.name" |
| | | :value="item.id" |
| | | |
| | | ></el-option> |
| | | <el-form-item label="上级分类" |
| | | prop="parentId"> |
| | | <el-select v-model="form.parentId" |
| | | placeholder="请选择上级分类"> |
| | | <el-option label="无上级分类" |
| | | :value="null"></el-option> |
| | | <el-option v-for="(item, index) in categories" |
| | | :key="index" |
| | | :label="item.name" |
| | | :value="item.id"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false" :disabled="formLoading">取消</el-button> |
| | | <el-button type="primary" @click="submitForm" :loading="formLoading">确定</el-button> |
| | | <el-button type="primary" |
| | | @click="submitForm" |
| | | :loading="formLoading">确定</el-button> |
| | | <el-button @click="dialogVisible = false" |
| | | :disabled="formLoading">取消</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, onMounted, reactive, watch } from 'vue'; |
| | | import { ElMessage, ElMessageBox } from 'element-plus'; |
| | | import { getSparePartsList, addSparePart, editSparePart, delSparePart,getSparePartsTree } from "@/api/equipmentManagement/spareParts"; |
| | | import { ref, computed, onMounted, reactive, watch } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import { |
| | | getSparePartsList, |
| | | addSparePart, |
| | | editSparePart, |
| | | delSparePart, |
| | | getSparePartsTree, |
| | | } from "@/api/equipmentManagement/spareParts"; |
| | | |
| | | // 加载状态 |
| | | const loading = ref(false); |
| | | const formLoading = ref(false); |
| | | // 对话框显示状态 |
| | | const dialogVisible = ref(false); |
| | | // 编辑 ID |
| | | const editId = ref(null); |
| | | // 表格数据 |
| | | const categories = ref([]); |
| | | // 渲染用的表格数据 |
| | | // const renderTableData = computed(() => buildTree(categories.value)); |
| | | const renderTableData = ref([]); |
| | | const operationType = ref('add') |
| | | // 表单引用 |
| | | const formRef = ref(null); |
| | | // 表单数据 |
| | | const form = reactive({ |
| | | id:'', |
| | | name: '', |
| | | sparePartsNo: '', |
| | | status: '', |
| | | description: '', |
| | | parentId: null |
| | | }); |
| | | |
| | | // 表单验证规则 |
| | | const rules = reactive({ |
| | | name: [ |
| | | { required: true, message: '请输入分类名称', trigger: 'blur' } |
| | | ], |
| | | sparePartsNo: [ |
| | | { required: true, message: '请输入分类编号', trigger: 'blur' } |
| | | ], |
| | | status: [ |
| | | { required: true, message: '请选择状态', trigger: 'change' } |
| | | ] |
| | | }); |
| | | // 获取缩进量 |
| | | const getIndentation = (row) => { |
| | | // 这里简单返回 20,可根据实际需求实现层级缩进逻辑 |
| | | return 20; |
| | | }; |
| | | // 定义 buildTree 函数 |
| | | const buildTree = (flatData) => { |
| | | const map = {}; |
| | | const result = []; |
| | | if(flatData){ |
| | | return result; |
| | | } |
| | | flatData.forEach(item => { |
| | | map[item.id] = { ...item, children: [] }; |
| | | // 加载状态 |
| | | const loading = ref(false); |
| | | const formLoading = ref(false); |
| | | // 对话框显示状态 |
| | | const dialogVisible = ref(false); |
| | | // 编辑 ID |
| | | const editId = ref(null); |
| | | // 表格数据 |
| | | const categories = ref([]); |
| | | // 渲染用的表格数据 |
| | | // const renderTableData = computed(() => buildTree(categories.value)); |
| | | const renderTableData = ref([]); |
| | | const operationType = ref("add"); |
| | | // 表单引用 |
| | | const formRef = ref(null); |
| | | // 表单数据 |
| | | const form = reactive({ |
| | | id: "", |
| | | name: "", |
| | | sparePartsNo: "", |
| | | status: "", |
| | | description: "", |
| | | parentId: null, |
| | | }); |
| | | flatData.forEach(item => { |
| | | if (item.parentId === null || !map[item.parentId]) { |
| | | result.push(map[item.id]); |
| | | } else { |
| | | map[item.parentId].children.push(map[item.id]); |
| | | } |
| | | |
| | | // 表单验证规则 |
| | | const rules = reactive({ |
| | | name: [{ required: true, message: "请输入分类名称", trigger: "blur" }], |
| | | sparePartsNo: [ |
| | | { required: true, message: "请输入分类编号", trigger: "blur" }, |
| | | ], |
| | | status: [{ required: true, message: "请选择状态", trigger: "change" }], |
| | | }); |
| | | return result; |
| | | }; |
| | | //获取树形结构 |
| | | const fetchTreeData = async () => { |
| | | fetchCategories(); |
| | | try { |
| | | const res = await getSparePartsTree(); |
| | | if (res.code === 200) { |
| | | renderTableData.value = res.data; |
| | | } else { |
| | | ElMessage.error(res.message || '获取分类列表失败'); |
| | | // 获取缩进量 |
| | | const getIndentation = row => { |
| | | // 这里简单返回 20,可根据实际需求实现层级缩进逻辑 |
| | | return 20; |
| | | }; |
| | | // 定义 buildTree 函数 |
| | | const buildTree = flatData => { |
| | | const map = {}; |
| | | const result = []; |
| | | if (flatData) { |
| | | return result; |
| | | } |
| | | }catch (error) { |
| | | ElMessage.error('获取分类列表失败'); |
| | | } |
| | | } |
| | | |
| | | // 获取分类列表 |
| | | const fetchCategories = async () => { |
| | | loading.value = true; |
| | | try { |
| | | const res = await getSparePartsList(); |
| | | if (res.code === 200) { |
| | | categories.value = res.data.records; |
| | | } else { |
| | | ElMessage.error(res.message || '获取分类列表失败'); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error('获取分类列表失败'); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | |
| | | // 新增分类 |
| | | const addCategory = () => { |
| | | form.id = ''; |
| | | form.name = ''; |
| | | form.sparePartsNo = ''; |
| | | form.status = ''; |
| | | form.description = ''; |
| | | form.parentId = null; |
| | | operationType.value = 'add' |
| | | dialogVisible.value = true; |
| | | console.log('dialogVisible 更新为', dialogVisible.value); |
| | | }; |
| | | |
| | | // 编辑分类 |
| | | const editCategory = (row) => { |
| | | Object.assign(form, row); |
| | | operationType.value = 'edit' |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // 删除分类 |
| | | const deleteCategory = async (id) => { |
| | | try { |
| | | await ElMessageBox.confirm('此操作将永久删除该分类,是否继续?', '提示', { |
| | | confirmButtonText: '确定', |
| | | cancelButtonText: '取消', |
| | | type: 'warning' |
| | | flatData.forEach(item => { |
| | | map[item.id] = { ...item, children: [] }; |
| | | }); |
| | | loading.value = true; |
| | | const res = await delSparePart(id); |
| | | if (res.code === 200) { |
| | | ElMessage.success('删除成功'); |
| | | fetchTreeData(); |
| | | } else { |
| | | ElMessage.error(res.message || '删除失败'); |
| | | } |
| | | } catch (error) { |
| | | if (error !== 'cancel') { |
| | | ElMessage.error('删除失败'); |
| | | } |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | |
| | | // 提交表单 |
| | | const submitForm = async () => { |
| | | if (!formRef.value) return; |
| | | try { |
| | | await formRef.value.validate(); |
| | | formLoading.value = true; |
| | | if (operationType.value === 'edit') { |
| | | let res = await editSparePart(form); |
| | | if (res.code === 200) { |
| | | ElMessage.success('编辑成功'); |
| | | dialogVisible.value = false; |
| | | fetchTreeData(); |
| | | } |
| | | } else { |
| | | let res = await addSparePart(form); |
| | | if (res.code === 200) { |
| | | ElMessage.success('编辑成功'); |
| | | dialogVisible.value = false; |
| | | fetchTreeData(); |
| | | flatData.forEach(item => { |
| | | if (item.parentId === null || !map[item.parentId]) { |
| | | result.push(map[item.id]); |
| | | } else { |
| | | map[item.parentId].children.push(map[item.id]); |
| | | } |
| | | }); |
| | | return result; |
| | | }; |
| | | //获取树形结构 |
| | | const fetchTreeData = async () => { |
| | | fetchCategories(); |
| | | try { |
| | | const res = await getSparePartsTree(); |
| | | if (res.code === 200) { |
| | | renderTableData.value = res.data; |
| | | } else { |
| | | ElMessage.error(res.message || "获取分类列表失败"); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error("获取分类列表失败"); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error('请填写完整表单信息'); |
| | | } finally { |
| | | formLoading.value = false; |
| | | } |
| | | }; |
| | | }; |
| | | |
| | | // 组件挂载时获取分类列表 |
| | | onMounted(() => { |
| | | fetchCategories(); |
| | | fetchTreeData(); |
| | | }); |
| | | // 获取分类列表 |
| | | const fetchCategories = async () => { |
| | | loading.value = true; |
| | | try { |
| | | const res = await getSparePartsList(); |
| | | if (res.code === 200) { |
| | | categories.value = res.data.records; |
| | | } else { |
| | | ElMessage.error(res.message || "获取分类列表失败"); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error("获取分类列表失败"); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | |
| | | // 新增分类 |
| | | const addCategory = () => { |
| | | form.id = ""; |
| | | form.name = ""; |
| | | form.sparePartsNo = ""; |
| | | form.status = ""; |
| | | form.description = ""; |
| | | form.parentId = null; |
| | | operationType.value = "add"; |
| | | dialogVisible.value = true; |
| | | console.log("dialogVisible 更新为", dialogVisible.value); |
| | | }; |
| | | |
| | | // 编辑分类 |
| | | const editCategory = row => { |
| | | Object.assign(form, row); |
| | | operationType.value = "edit"; |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // 删除分类 |
| | | const deleteCategory = async id => { |
| | | try { |
| | | await ElMessageBox.confirm("此操作将永久删除该分类,是否继续?", "提示", { |
| | | confirmButtonText: "确定", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }); |
| | | loading.value = true; |
| | | const res = await delSparePart(id); |
| | | if (res.code === 200) { |
| | | ElMessage.success("删除成功"); |
| | | fetchTreeData(); |
| | | } else { |
| | | ElMessage.error(res.message || "删除失败"); |
| | | } |
| | | } catch (error) { |
| | | if (error !== "cancel") { |
| | | ElMessage.error("删除失败"); |
| | | } |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | |
| | | // 提交表单 |
| | | const submitForm = async () => { |
| | | if (!formRef.value) return; |
| | | try { |
| | | await formRef.value.validate(); |
| | | formLoading.value = true; |
| | | if (operationType.value === "edit") { |
| | | let res = await editSparePart(form); |
| | | if (res.code === 200) { |
| | | ElMessage.success("编辑成功"); |
| | | dialogVisible.value = false; |
| | | fetchTreeData(); |
| | | } |
| | | } else { |
| | | let res = await addSparePart(form); |
| | | if (res.code === 200) { |
| | | ElMessage.success("编辑成功"); |
| | | dialogVisible.value = false; |
| | | fetchTreeData(); |
| | | } |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error("请填写完整表单信息"); |
| | | } finally { |
| | | formLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | // 组件挂载时获取分类列表 |
| | | onMounted(() => { |
| | | fetchCategories(); |
| | | fetchTreeData(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .spare-part-category { |
| | | padding: 20px; |
| | | } |
| | | .table_list { |
| | | margin-top: unset; |
| | | } |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 10px; |
| | | align-items: center; |
| | | } |
| | | .spare-part-category { |
| | | padding: 20px; |
| | | } |
| | | .table_list { |
| | | margin-top: unset; |
| | | } |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 10px; |
| | | align-items: center; |
| | | } |
| | | |
| | | /* 嵌套树形结构样式 */ |
| | | .nested-tree-node { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | width: 100%; |
| | | padding: 0 4px; |
| | | height: 30px; |
| | | line-height: 30px; |
| | | } |
| | | /* 嵌套树形结构样式 */ |
| | | .nested-tree-node { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | width: 100%; |
| | | padding: 0 4px; |
| | | height: 30px; |
| | | line-height: 30px; |
| | | } |
| | | |
| | | .category-code { |
| | | color: #606266; |
| | | font-size: 12px; |
| | | margin-left: 8px; |
| | | } |
| | | .category-code { |
| | | color: #606266; |
| | | font-size: 12px; |
| | | margin-left: 8px; |
| | | } |
| | | |
| | | .tree-actions { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | margin-left: auto; |
| | | } |
| | | .tree-actions { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | margin-left: auto; |
| | | } |
| | | |
| | | /* 表格样式调整 */ |
| | | .el-table { |
| | | font-size: 14px; |
| | | } |
| | | /* 表格样式调整 */ |
| | | .el-table { |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .el-table__header-wrapper th { |
| | | background-color: #f5f7fa; |
| | | font-weight: 600; |
| | | } |
| | | .el-table__header-wrapper th { |
| | | background-color: #f5f7fa; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .el-table__row:hover > td { |
| | | background-color: #fafafa; |
| | | } |
| | | .el-table__row:hover > td { |
| | | background-color: #fafafa; |
| | | } |
| | | |
| | | /* 嵌套树形结构特定样式 */ |
| | | .nested-tree { |
| | | background-color: transparent; |
| | | } |
| | | /* 嵌套树形结构特定样式 */ |
| | | .nested-tree { |
| | | background-color: transparent; |
| | | } |
| | | |
| | | .nested-tree .el-tree-node__content { |
| | | height: auto; |
| | | background-color: transparent; |
| | | } |
| | | .nested-tree .el-tree-node__content { |
| | | height: auto; |
| | | background-color: transparent; |
| | | } |
| | | |
| | | /* 搜索框样式调整 */ |
| | | .actions .el-input { |
| | | margin-right: 10px; |
| | | width: 200px; |
| | | } |
| | | /* 搜索框样式调整 */ |
| | | .actions .el-input { |
| | | margin-right: 10px; |
| | | width: 200px; |
| | | } |
| | | |
| | | /* 按钮组样式 */ |
| | | .actions > div { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | /* 按钮组样式 */ |
| | | .actions > div { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | |
| | | /* 确保表格中的操作按钮不会被截断 */ |
| | | .el-table-column--fixed-right .el-button { |
| | | margin: 0 2px; |
| | | } |
| | | /* 确保表格中的操作按钮不会被截断 */ |
| | | .el-table-column--fixed-right .el-button { |
| | | margin: 0 2px; |
| | | } |
| | | |
| | | /* 树形节点内容样式 */ |
| | | .nested-tree .el-tree-node__expand-icon { |
| | | font-size: 12px; |
| | | margin-right: 4px; |
| | | } |
| | | /* 树形节点内容样式 */ |
| | | .nested-tree .el-tree-node__expand-icon { |
| | | font-size: 12px; |
| | | margin-right: 4px; |
| | | } |
| | | |
| | | /* 空状态样式 */ |
| | | .el-table .cell:empty::before { |
| | | content: '-'; |
| | | color: #c0c4cc; |
| | | } |
| | | /* 空状态样式 */ |
| | | .el-table .cell:empty::before { |
| | | content: "-"; |
| | | color: #c0c4cc; |
| | | } |
| | | |
| | | /* 展开/折叠功能样式 */ |
| | | .expand-icon { |
| | | display: inline-block; |
| | | width: 20px; |
| | | height: 20px; |
| | | text-align: center; |
| | | line-height: 20px; |
| | | cursor: pointer; |
| | | font-size: 12px; |
| | | color: #909399; |
| | | } |
| | | /* 展开/折叠功能样式 */ |
| | | .expand-icon { |
| | | display: inline-block; |
| | | width: 20px; |
| | | height: 20px; |
| | | text-align: center; |
| | | line-height: 20px; |
| | | cursor: pointer; |
| | | font-size: 12px; |
| | | color: #909399; |
| | | } |
| | | |
| | | .expand-icon.expanded { |
| | | color: #409eff; |
| | | } |
| | | .expand-icon.expanded { |
| | | color: #409eff; |
| | | } |
| | | |
| | | /* 展开内容样式 */ |
| | | .expand-content { |
| | | padding: 10px 20px; |
| | | } |
| | | /* 展开内容样式 */ |
| | | .expand-content { |
| | | padding: 10px 20px; |
| | | } |
| | | |
| | | /* 子级内容样式 */ |
| | | .child-content { |
| | | padding-left: 40px; |
| | | } |
| | | /* 子级内容样式 */ |
| | | .child-content { |
| | | padding-left: 40px; |
| | | } |
| | | |
| | | /* 无子分类提示样式 */ |
| | | .no-children { |
| | | padding: 10px 20px; |
| | | color: #909399; |
| | | font-size: 14px; |
| | | } |
| | | /* 无子分类提示样式 */ |
| | | .no-children { |
| | | padding: 10px 20px; |
| | | color: #909399; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | /* 确保展开的表格样式正确 */ |
| | | .el-table .el-table__expanded-cell { |
| | | background-color: #fafafa; |
| | | } |
| | | /* 确保展开的表格样式正确 */ |
| | | .el-table .el-table__expanded-cell { |
| | | background-color: #fafafa; |
| | | } |
| | | |
| | | /* 展开的子表格样式 */ |
| | | .el-table .el-table { |
| | | border-top: none; |
| | | border-bottom: none; |
| | | } |
| | | /* 展开的子表格样式 */ |
| | | .el-table .el-table { |
| | | border-top: none; |
| | | border-bottom: none; |
| | | } |
| | | |
| | | /* 展开的子表格单元格样式 */ |
| | | .expand-content .el-table__body-wrapper .el-table__row { |
| | | background-color: #ffffff; |
| | | } |
| | | /* 展开的子表格单元格样式 */ |
| | | .expand-content .el-table__body-wrapper .el-table__row { |
| | | background-color: #ffffff; |
| | | } |
| | | |
| | | .expand-content .el-table__body-wrapper .el-table__row:hover > td { |
| | | background-color: #fafafa; |
| | | } |
| | | .expand-content .el-table__body-wrapper .el-table__row:hover > td { |
| | | background-color: #fafafa; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search-form"> |
| | | <el-form :inline="true" :model="searchForm"> |
| | | <el-form :inline="true" |
| | | :model="searchForm"> |
| | | <el-form-item label="部门"> |
| | | <el-input |
| | | v-model="searchForm.department" |
| | | placeholder="请输入部门名称" |
| | | clearable |
| | | style="width: 200px" |
| | | /> |
| | | <el-input v-model="searchForm.department" |
| | | placeholder="请输入部门名称" |
| | | clearable |
| | | style="width: 200px" /> |
| | | </el-form-item> |
| | | <el-form-item label="姓名"> |
| | | <el-input |
| | | v-model="searchForm.name" |
| | | placeholder="请输入姓名" |
| | | clearable |
| | | style="width: 200px" |
| | | /> |
| | | <el-input v-model="searchForm.name" |
| | | placeholder="请输入姓名" |
| | | clearable |
| | | style="width: 200px" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch">搜索</el-button> |
| | | <el-button type="primary" |
| | | @click="handleSearch">搜索</el-button> |
| | | <el-button @click="handleReset">重置</el-button> |
| | | <el-button type="success" @click="handleAdd">新增</el-button> |
| | | <el-button type="success" |
| | | @click="handleAdd">新增</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | |
| | | <div class="table-container"> |
| | | <DynamicTable |
| | | ref="dynamicTableRef" |
| | | :data="tableData" |
| | | :dict-types="dictTypes" |
| | | :loading="loading" |
| | | :show-selection="true" |
| | | :show-actions="true" |
| | | :show-pagination="true" |
| | | :pagination="pagination" |
| | | height="calc(100vh - 280px)" |
| | | @selection-change="handleSelectionChange" |
| | | @edit="handleEdit" |
| | | @delete="handleDelete" |
| | | @select-change="handleSelectChange" |
| | | @input-change="handleInputChange" |
| | | @size-change="handleSizeChange" |
| | | @current-change="handleCurrentChange" |
| | | /> |
| | | <DynamicTable ref="dynamicTableRef" |
| | | :data="tableData" |
| | | :dict-types="dictTypes" |
| | | :loading="loading" |
| | | :show-selection="true" |
| | | :show-actions="true" |
| | | :show-pagination="true" |
| | | :pagination="pagination" |
| | | height="calc(100vh - 280px)" |
| | | @selection-change="handleSelectionChange" |
| | | @edit="handleEdit" |
| | | @delete="handleDelete" |
| | | @select-change="handleSelectChange" |
| | | @input-change="handleInputChange" |
| | | @size-change="handleSizeChange" |
| | | @current-change="handleCurrentChange" /> |
| | | </div> |
| | | |
| | | <!-- 新增/编辑对话框 --> |
| | | <el-dialog |
| | | v-model="dialogVisible" |
| | | :title="dialogTitle" |
| | | width="600px" |
| | | append-to-body |
| | | > |
| | | <el-form |
| | | ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="100px" |
| | | > |
| | | <el-form-item label="部门" prop="department"> |
| | | <el-input v-model="form.department" placeholder="请输入部门" /> |
| | | <el-dialog v-model="dialogVisible" |
| | | :title="dialogTitle" |
| | | width="600px" |
| | | append-to-body> |
| | | <el-form ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="100px"> |
| | | <el-form-item label="部门" |
| | | prop="department"> |
| | | <el-input v-model="form.department" |
| | | placeholder="请输入部门" /> |
| | | </el-form-item> |
| | | <el-form-item label="姓名" prop="name"> |
| | | <el-input v-model="form.name" placeholder="请输入姓名" /> |
| | | <el-form-item label="姓名" |
| | | prop="name"> |
| | | <el-input v-model="form.name" |
| | | placeholder="请输入姓名" /> |
| | | </el-form-item> |
| | | <el-form-item label="工号" prop="employeeId"> |
| | | <el-input v-model="form.employeeId" placeholder="请输入工号" /> |
| | | <el-form-item label="工号" |
| | | prop="employeeId"> |
| | | <el-input v-model="form.employeeId" |
| | | placeholder="请输入工号" /> |
| | | </el-form-item> |
| | | |
| | | <!-- 动态表单项:根据字典生成 --> |
| | | <el-form-item |
| | | v-for="dictItem in dynamicFormItems" |
| | | :key="dictItem.value" |
| | | :label="dictItem.label" |
| | | :prop="dictItem.value" |
| | | > |
| | | <el-select |
| | | v-model="form[dictItem.value]" |
| | | placeholder="请选择" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="option in dictItem.options" |
| | | :key="option.value" |
| | | :label="option.label" |
| | | :value="option.value" |
| | | /> |
| | | <el-form-item v-for="dictItem in dynamicFormItems" |
| | | :key="dictItem.value" |
| | | :label="dictItem.label" |
| | | :prop="dictItem.value"> |
| | | <el-select v-model="form[dictItem.value]" |
| | | placeholder="请选择" |
| | | style="width: 100%"> |
| | | <el-option v-for="option in dictItem.options" |
| | | :key="option.value" |
| | | :label="option.label" |
| | | :value="option.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="handleSubmit">确定</el-button> |
| | | <el-button @click="dialogVisible = false">取消</el-button> |
| | | <el-button type="primary" @click="handleSubmit">确定</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed, onMounted } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import DynamicTable from '@/components/DynamicTable/index.vue' |
| | | import { ref, reactive, computed, onMounted } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import DynamicTable from "@/components/DynamicTable/index.vue"; |
| | | |
| | | // 响应式数据 |
| | | const loading = ref(false) |
| | | const dialogVisible = ref(false) |
| | | const dialogTitle = ref('') |
| | | const editIndex = ref(-1) |
| | | const selectedRows = ref([]) |
| | | // 响应式数据 |
| | | const loading = ref(false); |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref(""); |
| | | const editIndex = ref(-1); |
| | | const selectedRows = ref([]); |
| | | |
| | | // 搜索表单 |
| | | const searchForm = reactive({ |
| | | department: '', |
| | | name: '' |
| | | }) |
| | | // 搜索表单 |
| | | const searchForm = reactive({ |
| | | department: "", |
| | | name: "", |
| | | }); |
| | | |
| | | // 表格数据 |
| | | const tableData = ref([ |
| | | { |
| | | id: 1, |
| | | department: '技术部', |
| | | name: '张三', |
| | | employeeId: 'EMP001', |
| | | status: '1', |
| | | level: '2', |
| | | position: '1' |
| | | }, |
| | | { |
| | | id: 2, |
| | | department: '人事部', |
| | | name: '李四', |
| | | employeeId: 'EMP002', |
| | | status: '0', |
| | | level: '1', |
| | | position: '2' |
| | | }, |
| | | { |
| | | id: 3, |
| | | department: '财务部', |
| | | name: '王五', |
| | | employeeId: 'EMP003', |
| | | status: '1', |
| | | level: '3', |
| | | position: '1' |
| | | } |
| | | ]) |
| | | |
| | | // 字典类型配置 |
| | | const dictTypes = ref([ |
| | | 'sys_normal_disable', // 状态字典 |
| | | 'sys_user_level', // 级别字典 |
| | | 'sys_user_position' // 职位字典 |
| | | ]) |
| | | |
| | | // 分页配置 |
| | | const pagination = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0 |
| | | }) |
| | | |
| | | // 表单数据 |
| | | const form = reactive({ |
| | | department: '', |
| | | name: '', |
| | | employeeId: '', |
| | | status: '', |
| | | level: '', |
| | | position: '' |
| | | }) |
| | | |
| | | // 表单验证规则 |
| | | const rules = { |
| | | department: [ |
| | | { required: true, message: '请输入部门', trigger: 'blur' } |
| | | ], |
| | | name: [ |
| | | { required: true, message: '请输入姓名', trigger: 'blur' } |
| | | ], |
| | | employeeId: [ |
| | | { required: true, message: '请输入工号', trigger: 'blur' } |
| | | ] |
| | | } |
| | | |
| | | // 动态表单项 |
| | | const dynamicFormItems = computed(() => { |
| | | // 这里可以根据字典数据动态生成表单项 |
| | | return [ |
| | | // 表格数据 |
| | | const tableData = ref([ |
| | | { |
| | | label: '状态', |
| | | value: 'status', |
| | | options: [ |
| | | { label: '启用', value: '1' }, |
| | | { label: '禁用', value: '0' } |
| | | ] |
| | | id: 1, |
| | | department: "技术部", |
| | | name: "张三", |
| | | employeeId: "EMP001", |
| | | status: "1", |
| | | level: "2", |
| | | position: "1", |
| | | }, |
| | | { |
| | | label: '级别', |
| | | value: 'level', |
| | | options: [ |
| | | { label: '初级', value: '1' }, |
| | | { label: '中级', value: '2' }, |
| | | { label: '高级', value: '3' } |
| | | ] |
| | | id: 2, |
| | | department: "人事部", |
| | | name: "李四", |
| | | employeeId: "EMP002", |
| | | status: "0", |
| | | level: "1", |
| | | position: "2", |
| | | }, |
| | | { |
| | | label: '职位', |
| | | value: 'position', |
| | | options: [ |
| | | { label: '员工', value: '1' }, |
| | | { label: '主管', value: '2' }, |
| | | { label: '经理', value: '3' } |
| | | ] |
| | | id: 3, |
| | | department: "财务部", |
| | | name: "王五", |
| | | employeeId: "EMP003", |
| | | status: "1", |
| | | level: "3", |
| | | position: "1", |
| | | }, |
| | | ]); |
| | | |
| | | // 字典类型配置 |
| | | const dictTypes = ref([ |
| | | "sys_normal_disable", // 状态字典 |
| | | "sys_user_level", // 级别字典 |
| | | "sys_user_position", // 职位字典 |
| | | ]); |
| | | |
| | | // 分页配置 |
| | | const pagination = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | // 表单数据 |
| | | const form = reactive({ |
| | | department: "", |
| | | name: "", |
| | | employeeId: "", |
| | | status: "", |
| | | level: "", |
| | | position: "", |
| | | }); |
| | | |
| | | // 表单验证规则 |
| | | const rules = { |
| | | department: [{ required: true, message: "请输入部门", trigger: "blur" }], |
| | | name: [{ required: true, message: "请输入姓名", trigger: "blur" }], |
| | | employeeId: [{ required: true, message: "请输入工号", trigger: "blur" }], |
| | | }; |
| | | |
| | | // 动态表单项 |
| | | const dynamicFormItems = computed(() => { |
| | | // 这里可以根据字典数据动态生成表单项 |
| | | return [ |
| | | { |
| | | label: "状态", |
| | | value: "status", |
| | | options: [ |
| | | { label: "启用", value: "1" }, |
| | | { label: "禁用", value: "0" }, |
| | | ], |
| | | }, |
| | | { |
| | | label: "级别", |
| | | value: "level", |
| | | options: [ |
| | | { label: "初级", value: "1" }, |
| | | { label: "中级", value: "2" }, |
| | | { label: "高级", value: "3" }, |
| | | ], |
| | | }, |
| | | { |
| | | label: "职位", |
| | | value: "position", |
| | | options: [ |
| | | { label: "员工", value: "1" }, |
| | | { label: "主管", value: "2" }, |
| | | { label: "经理", value: "3" }, |
| | | ], |
| | | }, |
| | | ]; |
| | | }); |
| | | |
| | | // 组件引用 |
| | | const dynamicTableRef = ref(null); |
| | | const formRef = ref(null); |
| | | |
| | | // 事件处理函数 |
| | | const handleSearch = () => { |
| | | // 实现搜索逻辑 |
| | | console.log("搜索条件:", searchForm); |
| | | ElMessage.success("搜索功能待实现"); |
| | | }; |
| | | |
| | | const handleReset = () => { |
| | | searchForm.department = ""; |
| | | searchForm.name = ""; |
| | | }; |
| | | |
| | | const handleAdd = () => { |
| | | dialogTitle.value = "新增员工"; |
| | | editIndex.value = -1; |
| | | resetForm(); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const handleEdit = (row, index) => { |
| | | dialogTitle.value = "编辑员工"; |
| | | editIndex.value = index; |
| | | Object.assign(form, row); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const handleDelete = async (row, index) => { |
| | | try { |
| | | await ElMessageBox.confirm("确定要删除这条记录吗?", "提示", { |
| | | type: "warning", |
| | | }); |
| | | |
| | | tableData.value.splice(index, 1); |
| | | ElMessage.success("删除成功"); |
| | | } catch (error) { |
| | | // 用户取消删除 |
| | | } |
| | | ] |
| | | }) |
| | | }; |
| | | |
| | | // 组件引用 |
| | | const dynamicTableRef = ref(null) |
| | | const formRef = ref(null) |
| | | const handleSelectionChange = selection => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | // 事件处理函数 |
| | | const handleSearch = () => { |
| | | // 实现搜索逻辑 |
| | | console.log('搜索条件:', searchForm) |
| | | ElMessage.success('搜索功能待实现') |
| | | } |
| | | const handleSelectChange = (row, prop, value) => { |
| | | console.log("选择变化:", row, prop, value); |
| | | // 可以在这里处理数据更新逻辑 |
| | | }; |
| | | |
| | | const handleReset = () => { |
| | | searchForm.department = '' |
| | | searchForm.name = '' |
| | | } |
| | | const handleInputChange = (row, prop, value) => { |
| | | console.log("输入变化:", row, prop, value); |
| | | // 可以在这里处理数据更新逻辑 |
| | | }; |
| | | |
| | | const handleAdd = () => { |
| | | dialogTitle.value = '新增员工' |
| | | editIndex.value = -1 |
| | | resetForm() |
| | | dialogVisible.value = true |
| | | } |
| | | const handleSizeChange = size => { |
| | | pagination.size = size; |
| | | // 重新加载数据 |
| | | }; |
| | | |
| | | const handleEdit = (row, index) => { |
| | | dialogTitle.value = '编辑员工' |
| | | editIndex.value = index |
| | | Object.assign(form, row) |
| | | dialogVisible.value = true |
| | | } |
| | | const handleCurrentChange = current => { |
| | | pagination.current = current; |
| | | // 重新加载数据 |
| | | }; |
| | | |
| | | const handleDelete = async (row, index) => { |
| | | try { |
| | | await ElMessageBox.confirm('确定要删除这条记录吗?', '提示', { |
| | | type: 'warning' |
| | | }) |
| | | |
| | | tableData.value.splice(index, 1) |
| | | ElMessage.success('删除成功') |
| | | } catch (error) { |
| | | // 用户取消删除 |
| | | } |
| | | } |
| | | const handleSubmit = async () => { |
| | | try { |
| | | await formRef.value.validate(); |
| | | |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection |
| | | } |
| | | |
| | | const handleSelectChange = (row, prop, value) => { |
| | | console.log('选择变化:', row, prop, value) |
| | | // 可以在这里处理数据更新逻辑 |
| | | } |
| | | |
| | | const handleInputChange = (row, prop, value) => { |
| | | console.log('输入变化:', row, prop, value) |
| | | // 可以在这里处理数据更新逻辑 |
| | | } |
| | | |
| | | const handleSizeChange = (size) => { |
| | | pagination.size = size |
| | | // 重新加载数据 |
| | | } |
| | | |
| | | const handleCurrentChange = (current) => { |
| | | pagination.current = current |
| | | // 重新加载数据 |
| | | } |
| | | |
| | | const handleSubmit = async () => { |
| | | try { |
| | | await formRef.value.validate() |
| | | |
| | | if (editIndex.value === -1) { |
| | | // 新增 |
| | | const newRow = { |
| | | id: Date.now(), |
| | | ...form |
| | | if (editIndex.value === -1) { |
| | | // 新增 |
| | | const newRow = { |
| | | id: Date.now(), |
| | | ...form, |
| | | }; |
| | | tableData.value.push(newRow); |
| | | ElMessage.success("新增成功"); |
| | | } else { |
| | | // 编辑 |
| | | Object.assign(tableData.value[editIndex.value], form); |
| | | ElMessage.success("编辑成功"); |
| | | } |
| | | tableData.value.push(newRow) |
| | | ElMessage.success('新增成功') |
| | | } else { |
| | | // 编辑 |
| | | Object.assign(tableData.value[editIndex.value], form) |
| | | ElMessage.success('编辑成功') |
| | | |
| | | dialogVisible.value = false; |
| | | } catch (error) { |
| | | console.error("表单验证失败:", error); |
| | | } |
| | | |
| | | dialogVisible.value = false |
| | | } catch (error) { |
| | | console.error('表单验证失败:', error) |
| | | } |
| | | } |
| | | }; |
| | | |
| | | const resetForm = () => { |
| | | Object.assign(form, { |
| | | department: '', |
| | | name: '', |
| | | employeeId: '', |
| | | status: '', |
| | | level: '', |
| | | position: '' |
| | | }) |
| | | formRef.value?.resetFields() |
| | | } |
| | | const resetForm = () => { |
| | | Object.assign(form, { |
| | | department: "", |
| | | name: "", |
| | | employeeId: "", |
| | | status: "", |
| | | level: "", |
| | | position: "", |
| | | }); |
| | | formRef.value?.resetFields(); |
| | | }; |
| | | |
| | | // 组件挂载时初始化数据 |
| | | onMounted(() => { |
| | | pagination.total = tableData.value.length |
| | | }) |
| | | // 组件挂载时初始化数据 |
| | | onMounted(() => { |
| | | pagination.total = tableData.value.length; |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .search-form { |
| | | margin-bottom: 20px; |
| | | padding: 20px; |
| | | background-color: #f5f5f5; |
| | | border-radius: 4px; |
| | | } |
| | | .search-form { |
| | | margin-bottom: 20px; |
| | | padding: 20px; |
| | | background-color: #f5f5f5; |
| | | border-radius: 4px; |
| | | } |
| | | |
| | | .table-container { |
| | | background-color: #fff; |
| | | border-radius: 4px; |
| | | padding: 20px; |
| | | } |
| | | .table-container { |
| | | background-color: #fff; |
| | | border-radius: 4px; |
| | | padding: 20px; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div class="sample"> |
| | | <div class="main-content" v-if="!isDetail"> |
| | | <div class="main-content" |
| | | v-if="!isDetail"> |
| | | <div class="search"> |
| | | <div class="search_thing"> |
| | | <div class="search_label">仓库名称:</div> |
| | | <div class="search_input"> |
| | | <el-select v-model="entity.warehouseId" placeholder="选择仓库" size="small" @change="warehouseChange"> |
| | | <el-option v-for="item in warehouse" :key="item.id" :label="item.label" :value="item.id"> |
| | | </el-option> |
| | | </el-select> |
| | | </div> |
| | | </div> |
| | | <div class="search_thing"> |
| | | <div class="search_label">货架:</div> |
| | | <div class="search_label">仓库名称:</div> |
| | | <div class="search_input"> |
| | | <el-select v-model="entity.shelfId" placeholder="选择货架" size="small" @change="handleShelf"> |
| | | <el-option v-for="item in shelf" :key="item.id" :label="item.label" :value="item.id"> |
| | | <el-select v-model="entity.warehouseId" |
| | | placeholder="选择仓库" |
| | | size="small" |
| | | @change="warehouseChange"> |
| | | <el-option v-for="item in warehouse" |
| | | :key="item.id" |
| | | :label="item.label" |
| | | :value="item.id"> |
| | | </el-option> |
| | | </el-select> |
| | | </div> |
| | | </div> |
| | | <!-- <div class="search_thing"> |
| | | <div class="search_thing"> |
| | | <div class="search_label">货架:</div> |
| | | <div class="search_input"> |
| | | <el-select v-model="entity.shelfId" |
| | | placeholder="选择货架" |
| | | size="small" |
| | | @change="handleShelf"> |
| | | <el-option v-for="item in shelf" |
| | | :key="item.id" |
| | | :label="item.label" |
| | | :value="item.id"> |
| | | </el-option> |
| | | </el-select> |
| | | </div> |
| | | </div> |
| | | <!-- <div class="search_thing"> |
| | | <el-button size="small" @click="handleShelf(entity.shelfId,'')">重置</el-button> |
| | | <el-button size="small" type="primary" @click="handleShelf(entity.shelfId)">查询</el-button> |
| | | </div> --> |
| | | <div class="btns"> |
| | | <el-button size="small" style="color:#3A7BFA" @click="keepVisible=true">维护</el-button> |
| | | <el-button size="small" style="color:#3A7BFA" @click="warehouseVisible=true,isEdit=false">添加仓库</el-button> |
| | | <el-button size="small" style="color:#3A7BFA" @click="shelvesVisible=true,isEdit=false" |
| | | :disabled="entity.warehouseId==null">添加货架</el-button> |
| | | <el-button size="small" |
| | | style="color:#3A7BFA" |
| | | @click="keepVisible=true">维护</el-button> |
| | | <el-button size="small" |
| | | style="color:#3A7BFA" |
| | | @click="warehouseVisible=true,isEdit=false">添加仓库</el-button> |
| | | <el-button size="small" |
| | | style="color:#3A7BFA" |
| | | @click="shelvesVisible=true,isEdit=false" |
| | | :disabled="entity.warehouseId==null">添加货架</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="table" v-loading="tableLoading"> |
| | | <table class="tables" style="table-layout:fixed;" v-if="tableList.length>0"> |
| | | <div class="table" |
| | | v-loading="tableLoading"> |
| | | <table class="tables" |
| | | style="table-layout:fixed;" |
| | | v-if="tableList.length>0"> |
| | | <tbody> |
| | | <tr v-for="(item,index) in tableList" :key="index"> |
| | | <td v-for="(m,i) in item" :key="i" class="content"> |
| | | <tr v-for="(item,index) in tableList" |
| | | :key="index"> |
| | | <td v-for="(m,i) in item" |
| | | :key="i" |
| | | class="content"> |
| | | <h4 v-if="m.row!=undefined">{{ m.row }} - {{ m.col }}</h4> |
| | | <ul> |
| | | <el-tooltip |
| | | effect="dark" |
| | | placement="top" |
| | | v-for="(n,j) in m.documentationDtoList" |
| | | :key="j"> |
| | | <el-tooltip effect="dark" |
| | | placement="top" |
| | | v-for="(n,j) in m.documentationDtoList" |
| | | :key="j"> |
| | | <template #content><span>{{ n.docName }}</span> |
| | | <span> [{{ n.docNumber }}]</span></template> |
| | | <li class="green" |
| | | @click="handelDetail(n)"> |
| | | @click="handelDetail(n)"> |
| | | <i></i> |
| | | <span>{{ n.docName }}</span> |
| | | <span> [{{ n.docNumber }}] <span :style="{ color: getStatusColor(n.docStatus) }">({{ n.docStatus }})</span></span> |
| | |
| | | </td> |
| | | </tr> |
| | | <tr> |
| | | <td v-for="(item,index) in rowList" :key="index" style="background: ghostwhite;height: 20px;">{{ item }} |
| | | <td v-for="(item,index) in rowList" |
| | | :key="index" |
| | | style="background: ghostwhite;height: 20px;">{{ item }} |
| | | </td> |
| | | </tr> |
| | | </tbody> |
| | | </table> |
| | | <span v-else style="color: rgb(144, 147, 153);display: inline-block;position: absolute;top: 60%;left: 50%;transform: translate(-50%,-50%);">暂无数据</span> |
| | | <span v-else |
| | | style="color: rgb(144, 147, 153);display: inline-block;position: absolute;top: 60%;left: 50%;transform: translate(-50%,-50%);">暂无数据</span> |
| | | </div> |
| | | </div> |
| | | <Detail v-else @hanldeBack="isDetail=false" :current="current" /> |
| | | |
| | | <Detail v-else |
| | | @hanldeBack="isDetail=false" |
| | | :current="current" /> |
| | | <!-- 库位维护对话框 --> |
| | | <el-dialog v-model="keepVisible" title="库位维护" width="350px" :append-to-body="true"> |
| | | <el-tree :data="warehouse" ref="tree" node-key="id" |
| | | highlight-current v-if="keepVisible" |
| | | empty-text="暂无数据"> |
| | | <el-dialog v-model="keepVisible" |
| | | title="库位维护" |
| | | width="350px" |
| | | :append-to-body="true"> |
| | | <el-tree :data="warehouse" |
| | | ref="tree" |
| | | node-key="id" |
| | | highlight-current |
| | | v-if="keepVisible" |
| | | empty-text="暂无数据"> |
| | | <template #default="{ node, data }"> |
| | | <div class="custom-tree-node" style="width: 100%;"> |
| | | <div class="custom-tree-node" |
| | | style="width: 100%;"> |
| | | <el-row style="width: 100%;display: flex;align-items: center;"> |
| | | <el-col :span="14"> |
| | | <span> |
| | | <el-icon v-if="node.level < 2" class="folder-icon"> |
| | | <el-icon v-if="node.level < 2" |
| | | class="folder-icon"> |
| | | <FolderOpened /> |
| | | </el-icon> |
| | | <el-icon v-else class="file-icon"> |
| | | <el-icon v-else |
| | | class="file-icon"> |
| | | <Document /> |
| | | </el-icon> |
| | | {{ data.label }} |
| | | </span> |
| | | </el-col> |
| | | <el-col :span="10" v-if="node.level<3"> |
| | | <el-button type="link" size="small" :icon="Edit" @click.stop="handleEdit(data,node.level)"> |
| | | <el-col :span="10" |
| | | v-if="node.level<3"> |
| | | <el-button type="link" |
| | | size="small" |
| | | :icon="Edit" |
| | | @click.stop="handleEdit(data,node.level)"> |
| | | </el-button> |
| | | <el-button type="danger" size="small" :icon="Delete" @click.stop="handleDelete(data,node.level)"> |
| | | <el-button type="danger" |
| | | size="small" |
| | | :icon="Delete" |
| | | @click.stop="handleDelete(data,node.level)"> |
| | | </el-button> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | </el-tree> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="keepVisible = false">确 定</el-button> |
| | | <el-button @click="keepVisible = false">取 消</el-button> |
| | | <el-button type="primary" @click="keepVisible = false" >确 定</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 仓库新增/修改对话框 --> |
| | | <el-dialog v-model="warehouseVisible" :title="isEdit?'仓库修改':'仓库新增'" width="350px"> |
| | | <el-dialog v-model="warehouseVisible" |
| | | :title="isEdit?'仓库修改':'仓库新增'" |
| | | width="350px"> |
| | | <el-row> |
| | | <el-col class="search_thing" :span="24"> |
| | | <el-col class="search_thing" |
| | | :span="24"> |
| | | <div class="search_label"><span class="required-span">* </span>仓库名称:</div> |
| | | <div class="search_input"> |
| | | <el-input v-model="name" size="small" @keyup.enter="confirmWarehouse"></el-input> |
| | | <el-input v-model="name" |
| | | size="small" |
| | | @keyup.enter="confirmWarehouse"></el-input> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="confirmWarehouse" |
| | | :loading="upLoadWarehouse">确 定</el-button> |
| | | <el-button @click="warehouseVisible = false">取 消</el-button> |
| | | <el-button type="primary" @click="confirmWarehouse" :loading="upLoadWarehouse">确 定</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 货架新增/修改对话框 --> |
| | | <el-dialog v-model="shelvesVisible" :title="isEdit?'货架修改':'货架新增'" width="350px"> |
| | | <el-dialog v-model="shelvesVisible" |
| | | :title="isEdit?'货架修改':'货架新增'" |
| | | width="350px"> |
| | | <el-row> |
| | | <el-col class="search_thing" :span="24"> |
| | | <el-col class="search_thing" |
| | | :span="24"> |
| | | <div class="search_label"><span class="required-span">* </span>货架名称:</div> |
| | | <div class="search_input"> |
| | | <el-input v-model="shelves.name" size="small"></el-input> |
| | | <el-input v-model="shelves.name" |
| | | size="small"></el-input> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col class="search_thing" :span="24"> |
| | | <el-col class="search_thing" |
| | | :span="24"> |
| | | <div class="search_label"><span class="required-span">* </span>货架层数:</div> |
| | | <div class="search_input"> |
| | | <el-input v-model="shelves.row" size="small"></el-input> |
| | | <el-input v-model="shelves.row" |
| | | size="small"></el-input> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col class="search_thing" :span="24"> |
| | | <el-col class="search_thing" |
| | | :span="24"> |
| | | <div class="search_label"><span class="required-span">* </span>货架列数:</div> |
| | | <div class="search_input"> |
| | | <el-input v-model="shelves.col" size="small"></el-input> |
| | | <el-input v-model="shelves.col" |
| | | size="small"></el-input> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="confirmShelves" |
| | | :loading="upLoadShelves">确 定</el-button> |
| | | <el-button @click="shelvesVisible = false">取 消</el-button> |
| | | <el-button type="primary" @click="confirmShelves" :loading="upLoadShelves">确 定</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, watch } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { Edit, Delete, FolderOpened, Document } from '@element-plus/icons-vue' |
| | | import { getWarehouseList, addWarehouse, updateWarehouse, deleteWarehouse, getWarehouseStructure, addShelf, updateShelf, deleteShelf } from '@/api/fileManagement/bookshelf' |
| | | import Detail from './detail.vue' |
| | | import { ref, reactive, onMounted, watch } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import { Edit, Delete, FolderOpened, Document } from "@element-plus/icons-vue"; |
| | | import { |
| | | getWarehouseList, |
| | | addWarehouse, |
| | | updateWarehouse, |
| | | deleteWarehouse, |
| | | getWarehouseStructure, |
| | | addShelf, |
| | | updateShelf, |
| | | deleteShelf, |
| | | } from "@/api/fileManagement/bookshelf"; |
| | | import Detail from "./detail.vue"; |
| | | |
| | | // 响应式数据 |
| | | const entity = reactive({ |
| | | warehouseId: null, |
| | | shelfId: null |
| | | }) |
| | | // 响应式数据 |
| | | const entity = reactive({ |
| | | warehouseId: null, |
| | | shelfId: null, |
| | | }); |
| | | |
| | | const warehouse = ref([]) |
| | | const shelf = ref([]) |
| | | const keepVisible = ref(false) |
| | | const warehouseVisible = ref(false) |
| | | const shelvesVisible = ref(false) |
| | | const upLoadWarehouse = ref(false) |
| | | const upLoadShelves = ref(false) |
| | | const tableList = ref([]) |
| | | const rowList = ref([]) |
| | | const value = ref('') |
| | | const name = ref('') |
| | | const shelves = reactive({}) |
| | | const isEdit = ref(false) |
| | | const isDetail = ref(false) |
| | | const currentEdit = ref(null) |
| | | const tableLoading = ref(false) |
| | | const current = ref({}) |
| | | const warehouse = ref([]); |
| | | const shelf = ref([]); |
| | | const keepVisible = ref(false); |
| | | const warehouseVisible = ref(false); |
| | | const shelvesVisible = ref(false); |
| | | const upLoadWarehouse = ref(false); |
| | | const upLoadShelves = ref(false); |
| | | const tableList = ref([]); |
| | | const rowList = ref([]); |
| | | const value = ref(""); |
| | | const name = ref(""); |
| | | const shelves = reactive({}); |
| | | const isEdit = ref(false); |
| | | const isDetail = ref(false); |
| | | const currentEdit = ref(null); |
| | | const tableLoading = ref(false); |
| | | const current = ref({}); |
| | | |
| | | // 模板引用 |
| | | const organization = ref(null) |
| | | // 模板引用 |
| | | const organization = ref(null); |
| | | |
| | | // 监听器 |
| | | watch(isEdit, (newVal) => { |
| | | if (!newVal) { |
| | | Object.keys(shelves).forEach(key => delete shelves[key]) |
| | | } |
| | | }) |
| | | |
| | | // 方法 |
| | | |
| | | const selectList = async () => { |
| | | // 这里需要替换为实际的API调用 |
| | | const res = await getWarehouseList() |
| | | warehouse.value = res.data |
| | | |
| | | if (warehouse.value.length == 0) { |
| | | entity.warehouseId = '' |
| | | entity.shelfId = '' |
| | | tableList.value = [] |
| | | } |
| | | |
| | | |
| | | |
| | | if (!entity.warehouseId && warehouse.value.length > 0) { |
| | | entity.warehouseId = warehouse.value[0].id |
| | | warehouseChange(entity.warehouseId) |
| | | if (shelf.value.length > 0) { |
| | | entity.shelfId = shelf.value[0].id |
| | | handleShelf(entity.shelfId) |
| | | } else { |
| | | tableList.value = [] |
| | | // 监听器 |
| | | watch(isEdit, newVal => { |
| | | if (!newVal) { |
| | | Object.keys(shelves).forEach(key => delete shelves[key]); |
| | | } |
| | | } else if (warehouse.value.length > 0) { |
| | | warehouseChange(entity.warehouseId) |
| | | if (shelf.value.length > 0) { |
| | | entity.shelfId = shelf.value[0].id |
| | | handleShelf(entity.shelfId) |
| | | } else { |
| | | tableList.value = [] |
| | | }); |
| | | |
| | | // 方法 |
| | | |
| | | const selectList = async () => { |
| | | // 这里需要替换为实际的API调用 |
| | | const res = await getWarehouseList(); |
| | | warehouse.value = res.data; |
| | | |
| | | if (warehouse.value.length == 0) { |
| | | entity.warehouseId = ""; |
| | | entity.shelfId = ""; |
| | | tableList.value = []; |
| | | } |
| | | } |
| | | } |
| | | |
| | | const confirmWarehouse = () => { |
| | | if (!name.value) { |
| | | ElMessage.error('请填写仓库名称') |
| | | return |
| | | } |
| | | upLoadWarehouse.value = true |
| | | |
| | | if (currentEdit.value && currentEdit.value.id) { |
| | | // 修改仓库 |
| | | // 这里需要替换为实际的API调用 |
| | | updateWarehouse({ |
| | | id: currentEdit.value.id, |
| | | warehouseName: name.value |
| | | }).then(res => { |
| | | upLoadWarehouse.value = false |
| | | warehouseVisible.value = false |
| | | currentEdit.value = null |
| | | ElMessage.success('修改成功') |
| | | selectList() |
| | | name.value = '' |
| | | warehouseChange(entity.warehouseId) |
| | | }) |
| | | |
| | | } else { |
| | | // 新增仓库 |
| | | // 这里需要替换为实际的API调用 |
| | | addWarehouse({ |
| | | warehouseName: name.value |
| | | }).then(res => { |
| | | upLoadWarehouse.value = false |
| | | warehouseVisible.value = false |
| | | ElMessage.success('添加成功') |
| | | selectList() |
| | | name.value = '' |
| | | warehouseChange(entity.warehouseId) |
| | | }) |
| | | } |
| | | } |
| | | if (!entity.warehouseId && warehouse.value.length > 0) { |
| | | entity.warehouseId = warehouse.value[0].id; |
| | | warehouseChange(entity.warehouseId); |
| | | if (shelf.value.length > 0) { |
| | | entity.shelfId = shelf.value[0].id; |
| | | handleShelf(entity.shelfId); |
| | | } else { |
| | | tableList.value = []; |
| | | } |
| | | } else if (warehouse.value.length > 0) { |
| | | warehouseChange(entity.warehouseId); |
| | | if (shelf.value.length > 0) { |
| | | entity.shelfId = shelf.value[0].id; |
| | | handleShelf(entity.shelfId); |
| | | } else { |
| | | tableList.value = []; |
| | | } |
| | | } |
| | | }; |
| | | |
| | | const confirmShelves = () => { |
| | | if (!shelves.name) { |
| | | ElMessage.error('请填写货架名称') |
| | | return |
| | | } |
| | | if (!shelves.row) { |
| | | ElMessage.error('请填写货架层数') |
| | | return |
| | | } |
| | | if (!shelves.col) { |
| | | ElMessage.error('请填写货架列数') |
| | | return |
| | | } |
| | | upLoadShelves.value = true |
| | | |
| | | if (currentEdit.value && currentEdit.value.id) { |
| | | // 修改 |
| | | updateShelf({ |
| | | id: currentEdit.value.id, |
| | | name: shelves.name, |
| | | row: Number(shelves.row), |
| | | col: Number(shelves.col), |
| | | warehouseId: entity.warehouseId |
| | | }).then(res => { |
| | | upLoadShelves.value = false |
| | | shelvesVisible.value = false |
| | | ElMessage.success('修改成功') |
| | | selectList() |
| | | currentEdit.value = {} |
| | | }).catch(err => { |
| | | upLoadShelves.value = false |
| | | shelvesVisible.value = false |
| | | ElMessage.error('修改失败') |
| | | }) |
| | | |
| | | } else { |
| | | // 新增 |
| | | // 这里需要替换为实际的API调用 |
| | | addShelf({ |
| | | name: shelves.name, |
| | | row: Number(shelves.row), |
| | | col: Number(shelves.col), |
| | | warehouseId: entity.warehouseId |
| | | }).then(res => { |
| | | upLoadShelves.value = false |
| | | shelvesVisible.value = false |
| | | ElMessage.success('添加成功') |
| | | selectList() |
| | | Object.keys(shelves).forEach(key => delete shelves[key]) |
| | | }).catch(err => { |
| | | upLoadShelves.value = false |
| | | shelvesVisible.value = false |
| | | ElMessage.error('添加失败') |
| | | }) |
| | | } |
| | | warehouseChange(entity.warehouseId) |
| | | } |
| | | const confirmWarehouse = () => { |
| | | if (!name.value) { |
| | | ElMessage.error("请填写仓库名称"); |
| | | return; |
| | | } |
| | | upLoadWarehouse.value = true; |
| | | |
| | | |
| | | |
| | | const handleDelete = (row, level) => { |
| | | ElMessageBox.confirm('是否删除当前数据?', "警告", { |
| | | confirmButtonText: "确定", |
| | | cancelButtonText: "取消", |
| | | type: "warning" |
| | | }).then(() => { |
| | | if (level == 1) { |
| | | // 删除仓库 |
| | | deleteWarehouse([row.id]).then(res => { |
| | | ElMessage.success('删除成功') |
| | | selectList() |
| | | }) |
| | | } else { |
| | | // 删除货架 |
| | | deleteShelf({ |
| | | id: row.id |
| | | if (currentEdit.value && currentEdit.value.id) { |
| | | // 修改仓库 |
| | | // 这里需要替换为实际的API调用 |
| | | updateWarehouse({ |
| | | id: currentEdit.value.id, |
| | | warehouseName: name.value, |
| | | }).then(res => { |
| | | ElMessage.success('删除成功') |
| | | selectList() |
| | | }) |
| | | upLoadWarehouse.value = false; |
| | | warehouseVisible.value = false; |
| | | currentEdit.value = null; |
| | | ElMessage.success("修改成功"); |
| | | selectList(); |
| | | name.value = ""; |
| | | warehouseChange(entity.warehouseId); |
| | | }); |
| | | } else { |
| | | // 新增仓库 |
| | | // 这里需要替换为实际的API调用 |
| | | addWarehouse({ |
| | | warehouseName: name.value, |
| | | }).then(res => { |
| | | upLoadWarehouse.value = false; |
| | | warehouseVisible.value = false; |
| | | ElMessage.success("添加成功"); |
| | | selectList(); |
| | | name.value = ""; |
| | | warehouseChange(entity.warehouseId); |
| | | }); |
| | | } |
| | | warehouseChange(entity.warehouseId) |
| | | }).catch(() => {}) |
| | | } |
| | | }; |
| | | |
| | | const handleEdit = (data, level) => { |
| | | isEdit.value = true |
| | | if (level == 1) { |
| | | warehouseVisible.value = true |
| | | currentEdit.value = data |
| | | name.value = data.label |
| | | } else { |
| | | shelvesVisible.value = true |
| | | currentEdit.value = data |
| | | Object.assign(shelves, { |
| | | name: data.label, |
| | | row: data.row, |
| | | col: data.col, |
| | | warehouseId: data.warehouseId |
| | | }) |
| | | } |
| | | } |
| | | |
| | | const handelDetail = (row) => { |
| | | current.value = row |
| | | isDetail.value = true |
| | | } |
| | | |
| | | // 根据文档状态返回对应的颜色 |
| | | const getStatusColor = (status) => { |
| | | if (status === '正常') { |
| | | return '#34BD66' // 绿色 |
| | | } else if (status === '借出') { |
| | | return '#F56C6C' // 红色 |
| | | } |
| | | return '#606266' // 默认颜色 |
| | | } |
| | | |
| | | const warehouseChange = (val) => { |
| | | tableList.value = [] |
| | | let map = warehouse.value.find(a => { |
| | | return a && a.id === val ? a : null |
| | | }) |
| | | if (map && map.children) { |
| | | shelf.value = map.children |
| | | entity.shelfId = '' |
| | | } else { |
| | | shelf.value = [] |
| | | } |
| | | currentEdit.value = null |
| | | } |
| | | |
| | | const handleShelf = async(e) => { |
| | | if (e) { |
| | | tableLoading.value = true |
| | | let data = [] |
| | | const res = await getWarehouseStructure({warehouseGoodsShelvesId:e}) |
| | | if(res.code == 200){ |
| | | data = res.data.map(m=>{ |
| | | m.books = m.documentationDtoList|[] |
| | | return m |
| | | }) |
| | | }else{ |
| | | ElMessage.error(res.message) |
| | | const confirmShelves = () => { |
| | | if (!shelves.name) { |
| | | ElMessage.error("请填写货架名称"); |
| | | return; |
| | | } |
| | | setTimeout(() => { |
| | | tableLoading.value = false |
| | | let set = new Set() |
| | | tableList.value = [] |
| | | let arr = [] |
| | | |
| | | if (data && data.length > 0) { |
| | | data.forEach(m => { |
| | | if (m && m.row && m.col) { |
| | | set.add(m.col) |
| | | if (arr.length > 0) { |
| | | if (arr.find(n => n.row == m.row)) { |
| | | arr.push(m) |
| | | } else { |
| | | tableList.value.push(arr) |
| | | arr = [] |
| | | arr.push(m) |
| | | } |
| | | } else { |
| | | arr.push(m) |
| | | } |
| | | } |
| | | if (!shelves.row) { |
| | | ElMessage.error("请填写货架层数"); |
| | | return; |
| | | } |
| | | if (!shelves.col) { |
| | | ElMessage.error("请填写货架列数"); |
| | | return; |
| | | } |
| | | upLoadShelves.value = true; |
| | | |
| | | if (currentEdit.value && currentEdit.value.id) { |
| | | // 修改 |
| | | updateShelf({ |
| | | id: currentEdit.value.id, |
| | | name: shelves.name, |
| | | row: Number(shelves.row), |
| | | col: Number(shelves.col), |
| | | warehouseId: entity.warehouseId, |
| | | }) |
| | | .then(res => { |
| | | upLoadShelves.value = false; |
| | | shelvesVisible.value = false; |
| | | ElMessage.success("修改成功"); |
| | | selectList(); |
| | | currentEdit.value = {}; |
| | | }) |
| | | |
| | | if (arr.length > 0) { |
| | | tableList.value.push(arr) |
| | | .catch(err => { |
| | | upLoadShelves.value = false; |
| | | shelvesVisible.value = false; |
| | | ElMessage.error("修改失败"); |
| | | }); |
| | | } else { |
| | | // 新增 |
| | | // 这里需要替换为实际的API调用 |
| | | addShelf({ |
| | | name: shelves.name, |
| | | row: Number(shelves.row), |
| | | col: Number(shelves.col), |
| | | warehouseId: entity.warehouseId, |
| | | }) |
| | | .then(res => { |
| | | upLoadShelves.value = false; |
| | | shelvesVisible.value = false; |
| | | ElMessage.success("添加成功"); |
| | | selectList(); |
| | | Object.keys(shelves).forEach(key => delete shelves[key]); |
| | | }) |
| | | .catch(err => { |
| | | upLoadShelves.value = false; |
| | | shelvesVisible.value = false; |
| | | ElMessage.error("添加失败"); |
| | | }); |
| | | } |
| | | warehouseChange(entity.warehouseId); |
| | | }; |
| | | |
| | | const handleDelete = (row, level) => { |
| | | ElMessageBox.confirm("是否删除当前数据?", "警告", { |
| | | confirmButtonText: "确定", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | if (level == 1) { |
| | | // 删除仓库 |
| | | deleteWarehouse([row.id]).then(res => { |
| | | ElMessage.success("删除成功"); |
| | | selectList(); |
| | | }); |
| | | } else { |
| | | // 删除货架 |
| | | deleteShelf({ |
| | | id: row.id, |
| | | }).then(res => { |
| | | ElMessage.success("删除成功"); |
| | | selectList(); |
| | | }); |
| | | } |
| | | warehouseChange(entity.warehouseId); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | const handleEdit = (data, level) => { |
| | | isEdit.value = true; |
| | | if (level == 1) { |
| | | warehouseVisible.value = true; |
| | | currentEdit.value = data; |
| | | name.value = data.label; |
| | | } else { |
| | | shelvesVisible.value = true; |
| | | currentEdit.value = data; |
| | | Object.assign(shelves, { |
| | | name: data.label, |
| | | row: data.row, |
| | | col: data.col, |
| | | warehouseId: data.warehouseId, |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | const handelDetail = row => { |
| | | current.value = row; |
| | | isDetail.value = true; |
| | | }; |
| | | |
| | | // 根据文档状态返回对应的颜色 |
| | | const getStatusColor = status => { |
| | | if (status === "正常") { |
| | | return "#34BD66"; // 绿色 |
| | | } else if (status === "借出") { |
| | | return "#F56C6C"; // 红色 |
| | | } |
| | | return "#606266"; // 默认颜色 |
| | | }; |
| | | |
| | | const warehouseChange = val => { |
| | | tableList.value = []; |
| | | let map = warehouse.value.find(a => { |
| | | return a && a.id === val ? a : null; |
| | | }); |
| | | if (map && map.children) { |
| | | shelf.value = map.children; |
| | | entity.shelfId = ""; |
| | | } else { |
| | | shelf.value = []; |
| | | } |
| | | currentEdit.value = null; |
| | | }; |
| | | |
| | | const handleShelf = async e => { |
| | | if (e) { |
| | | tableLoading.value = true; |
| | | let data = []; |
| | | const res = await getWarehouseStructure({ warehouseGoodsShelvesId: e }); |
| | | if (res.code == 200) { |
| | | data = res.data.map(m => { |
| | | m.books = m.documentationDtoList | []; |
| | | return m; |
| | | }); |
| | | } else { |
| | | ElMessage.error(res.message); |
| | | } |
| | | |
| | | rowList.value = [] |
| | | for (let i = 0; i < set.size; i++) { |
| | | rowList.value.push(`${i + 1} 列`) |
| | | } |
| | | console.log(6666, tableList.value,rowList.value,data) |
| | | }, 1000) |
| | | } |
| | | } |
| | | setTimeout(() => { |
| | | tableLoading.value = false; |
| | | let set = new Set(); |
| | | tableList.value = []; |
| | | let arr = []; |
| | | |
| | | if (data && data.length > 0) { |
| | | data.forEach(m => { |
| | | if (m && m.row && m.col) { |
| | | set.add(m.col); |
| | | if (arr.length > 0) { |
| | | if (arr.find(n => n.row == m.row)) { |
| | | arr.push(m); |
| | | } else { |
| | | tableList.value.push(arr); |
| | | arr = []; |
| | | arr.push(m); |
| | | } |
| | | } else { |
| | | arr.push(m); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | if (arr.length > 0) { |
| | | tableList.value.push(arr); |
| | | } |
| | | } |
| | | |
| | | // 生命周期 |
| | | onMounted(() => { |
| | | selectList() |
| | | }) |
| | | rowList.value = []; |
| | | for (let i = 0; i < set.size; i++) { |
| | | rowList.value.push(`${i + 1} 列`); |
| | | } |
| | | console.log(6666, tableList.value, rowList.value, data); |
| | | }, 1000); |
| | | } |
| | | }; |
| | | |
| | | // 生命周期 |
| | | onMounted(() => { |
| | | selectList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | |
| | | } |
| | | |
| | | li:hover i { |
| | | background: #3A7BFA; |
| | | background: #3a7bfa; |
| | | } |
| | | |
| | | li:hover .num { |
| | | color: #3A7BFA; |
| | | color: #3a7bfa; |
| | | } |
| | | |
| | | .green { |
| | | background: #E0F6EA; |
| | | background: #e0f6ea; |
| | | } |
| | | |
| | | .green i { |
| | | background: #34BD66; |
| | | background: #34bd66; |
| | | } |
| | | |
| | | .green .num { |
| | | color: #34BD66; |
| | | color: #34bd66; |
| | | } |
| | | |
| | | .el-dialog { |
| | |
| | | display: flex; |
| | | align-items: center; |
| | | font-size: 14px; |
| | | color: #3A7BFA; |
| | | color: #3a7bfa; |
| | | position: absolute; |
| | | top: 23px; |
| | | right: 54px; |
| | |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="closeDialog">取消</el-button> |
| | | <el-button type="primary" @click="submitForm">确认</el-button> |
| | | <el-button @click="closeDialog">取消</el-button> |
| | | |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | <template> |
| | | <div class="milestone-list-container"> |
| | | <el-timeline> |
| | | <el-timeline-item |
| | | v-for="milestone in milestoneList" |
| | | :key="milestone.phaseId" |
| | | :timestamp="milestone.endDate" |
| | | > |
| | | <el-timeline-item v-for="milestone in milestoneList" |
| | | :key="milestone.phaseId" |
| | | :timestamp="milestone.endDate"> |
| | | <el-card> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>{{ milestone.phaseName }}</span> |
| | | <div class="milestone-actions"> |
| | | <el-button type="text" size="small" @click="handleEdit(milestone)">编辑</el-button> |
| | | <el-button type="text" size="small" @click="handleDelete(milestone)" danger>删除</el-button> |
| | | <el-button type="text" |
| | | size="small" |
| | | @click="handleEdit(milestone)">编辑</el-button> |
| | | <el-button type="text" |
| | | size="small" |
| | | @click="handleDelete(milestone)" |
| | | danger>删除</el-button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | |
| | | </el-card> |
| | | </el-timeline-item> |
| | | </el-timeline> |
| | | |
| | | <!-- 无里程碑时的提示 --> |
| | | <div v-if="milestoneList.length === 0" class="empty-tip"> |
| | | <div v-if="milestoneList.length === 0" |
| | | class="empty-tip"> |
| | | <el-empty description="暂无里程碑数据" /> |
| | | </div> |
| | | |
| | | <!-- 编辑里程碑对话框 --> |
| | | <el-dialog |
| | | v-model="dialogVisible" |
| | | :title="'编辑里程碑: ' + (form.phaseName || '')" |
| | | width="600px" |
| | | :close-on-click-modal="false" |
| | | > |
| | | <el-form |
| | | ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="100px" |
| | | > |
| | | <el-form-item label="里程碑名称" prop="phaseName"> |
| | | <el-input v-model="form.phaseName" placeholder="请输入里程碑名称" /> |
| | | <el-dialog v-model="dialogVisible" |
| | | :title="'编辑里程碑: ' + (form.phaseName || '')" |
| | | width="600px" |
| | | :close-on-click-modal="false"> |
| | | <el-form ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="100px"> |
| | | <el-form-item label="里程碑名称" |
| | | prop="phaseName"> |
| | | <el-input v-model="form.phaseName" |
| | | placeholder="请输入里程碑名称" /> |
| | | </el-form-item> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="开始日期" prop="startDate"> |
| | | <el-date-picker |
| | | v-model="form.startDate" |
| | | type="date" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | placeholder="选择开始日期" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="结束日期" prop="endDate"> |
| | | <el-date-picker |
| | | v-model="form.endDate" |
| | | type="date" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | placeholder="选择结束日期" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="状态" prop="status"> |
| | | <el-select v-model="form.status" placeholder="请选择状态"> |
| | | <el-option label="未开始" value="notStarted" /> |
| | | <el-option label="已完成" value="completed" /> |
| | | <el-option label="已延迟" value="delayed" /> |
| | | <el-col :span="12"> |
| | | <el-form-item label="开始日期" |
| | | prop="startDate"> |
| | | <el-date-picker v-model="form.startDate" |
| | | type="date" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | placeholder="选择开始日期" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="结束日期" |
| | | prop="endDate"> |
| | | <el-date-picker v-model="form.endDate" |
| | | type="date" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | placeholder="选择结束日期" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="状态" |
| | | prop="status"> |
| | | <el-select v-model="form.status" |
| | | placeholder="请选择状态"> |
| | | <el-option label="未开始" |
| | | value="notStarted" /> |
| | | <el-option label="已完成" |
| | | value="completed" /> |
| | | <el-option label="已延迟" |
| | | value="delayed" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="submitEditForm">确定</el-button> |
| | | <el-button @click="dialogVisible = false">取消</el-button> |
| | | <el-button type="primary" @click="submitEditForm">确定</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, watch, reactive } from 'vue'; |
| | | import { ElMessage, ElMessageBox } from 'element-plus'; |
| | | import { getProject, listProjectPhase, updateProjectPhase,delProjectPhase } from '@/api/oaSystem/projectManagement'; |
| | | import { ref, onMounted, watch, reactive } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import { |
| | | getProject, |
| | | listProjectPhase, |
| | | updateProjectPhase, |
| | | delProjectPhase, |
| | | } from "@/api/oaSystem/projectManagement"; |
| | | |
| | | const props = defineProps({ |
| | | projectId: { |
| | | type: String, |
| | | required: true |
| | | } |
| | | }); |
| | | |
| | | const emit = defineEmits(['refresh']); |
| | | |
| | | const milestoneList = ref([]); |
| | | const dialogVisible = ref(false); |
| | | const formRef = ref(null); |
| | | const form = reactive({ |
| | | phaseId: '', |
| | | phaseName: '', |
| | | startDate: '', |
| | | endDate: '', |
| | | status: 'notStarted', |
| | | projectId: props.projectId |
| | | }); |
| | | |
| | | // 表单验证规则 |
| | | const rules = { |
| | | phaseName: [ |
| | | { required: true, message: '请输入里程碑名称', trigger: 'blur' }, |
| | | { min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' } |
| | | ], |
| | | status: [ |
| | | { required: true, message: '请选择状态', trigger: 'change' } |
| | | ] |
| | | }; |
| | | |
| | | // 获取里程碑列表 |
| | | const getMilestoneList = async () => { |
| | | try { |
| | | listProjectPhase(props.projectId).then(res => { |
| | | milestoneList.value = res.data.rows || res.data; |
| | | // 按目标日期排序 |
| | | // milestoneList.value.sort((a, b) => new Date(a.endDate) - new Date(b.endDate)); |
| | | }) |
| | | } catch (error) { |
| | | ElMessage.error('获取里程碑列表失败'); |
| | | console.error('获取里程碑列表失败:', error); |
| | | } |
| | | }; |
| | | |
| | | // 编辑里程碑 |
| | | const handleEdit = (milestone) => { |
| | | // 复制里程碑数据到表单 |
| | | Object.assign(form, { |
| | | phaseId: milestone.phaseId, |
| | | phaseName: milestone.phaseName, |
| | | description: milestone.description, |
| | | endDate: milestone.endDate, |
| | | status: milestone.status, |
| | | projectId: props.projectId |
| | | const props = defineProps({ |
| | | projectId: { |
| | | type: String, |
| | | required: true, |
| | | }, |
| | | }); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // 提交编辑表单 |
| | | const submitEditForm = async () => { |
| | | try { |
| | | await formRef.value.validate(); |
| | | |
| | | // 发送更新请求 |
| | | const res = await updateProjectPhase(form); |
| | | |
| | | if (res.code === 200) { |
| | | ElMessage.success('里程碑编辑成功'); |
| | | dialogVisible.value = false; |
| | | getMilestoneList(); // 刷新列表 |
| | | emit('refresh'); // 通知父组件刷新 |
| | | } else { |
| | | ElMessage.error(res.msg || '里程碑编辑失败'); |
| | | } |
| | | } catch (error) { |
| | | if (error.name === 'ValidationError') { |
| | | // 表单验证失败,Element Plus会自动提示 |
| | | return; |
| | | } |
| | | ElMessage.error('里程碑编辑失败'); |
| | | console.error('编辑里程碑失败:', error); |
| | | } |
| | | }; |
| | | const emit = defineEmits(["refresh"]); |
| | | |
| | | // 删除里程碑 |
| | | const handleDelete = (milestone) => { |
| | | ElMessageBox.confirm( |
| | | `确定要删除里程碑 "${milestone.phaseName}" 吗?删除后将无法恢复。`, |
| | | '删除确认', |
| | | { |
| | | confirmButtonText: '确定', |
| | | cancelButtonText: '取消', |
| | | type: 'warning' |
| | | const milestoneList = ref([]); |
| | | const dialogVisible = ref(false); |
| | | const formRef = ref(null); |
| | | const form = reactive({ |
| | | phaseId: "", |
| | | phaseName: "", |
| | | startDate: "", |
| | | endDate: "", |
| | | status: "notStarted", |
| | | projectId: props.projectId, |
| | | }); |
| | | |
| | | // 表单验证规则 |
| | | const rules = { |
| | | phaseName: [ |
| | | { required: true, message: "请输入里程碑名称", trigger: "blur" }, |
| | | { min: 2, max: 50, message: "长度在 2 到 50 个字符", trigger: "blur" }, |
| | | ], |
| | | status: [{ required: true, message: "请选择状态", trigger: "change" }], |
| | | }; |
| | | |
| | | // 获取里程碑列表 |
| | | const getMilestoneList = async () => { |
| | | try { |
| | | listProjectPhase(props.projectId).then(res => { |
| | | milestoneList.value = res.data.rows || res.data; |
| | | // 按目标日期排序 |
| | | // milestoneList.value.sort((a, b) => new Date(a.endDate) - new Date(b.endDate)); |
| | | }); |
| | | } catch (error) { |
| | | ElMessage.error("获取里程碑列表失败"); |
| | | console.error("获取里程碑列表失败:", error); |
| | | } |
| | | ) |
| | | .then(async () => { |
| | | try { |
| | | // 调用删除API |
| | | const res = await delProjectPhase(milestone.phaseId); |
| | | |
| | | if (res.code === 200) { |
| | | ElMessage.success('里程碑删除成功'); |
| | | getMilestoneList(); // 刷新列表 |
| | | emit('refresh'); // 通知父组件刷新 |
| | | } else { |
| | | ElMessage.error(res.msg || '里程碑删除失败'); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error('里程碑删除失败'); |
| | | console.error('删除里程碑失败:', error); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | // 用户取消删除 |
| | | ElMessage.info('已取消删除'); |
| | | }; |
| | | |
| | | // 编辑里程碑 |
| | | const handleEdit = milestone => { |
| | | // 复制里程碑数据到表单 |
| | | Object.assign(form, { |
| | | phaseId: milestone.phaseId, |
| | | phaseName: milestone.phaseName, |
| | | description: milestone.description, |
| | | endDate: milestone.endDate, |
| | | status: milestone.status, |
| | | projectId: props.projectId, |
| | | }); |
| | | }; |
| | | |
| | | // 获取状态标签类型 |
| | | const getStatusType = (status) => { |
| | | const statusTypeMap = { |
| | | notStarted: 'info', |
| | | completed: 'success', |
| | | delayed: 'danger' |
| | | dialogVisible.value = true; |
| | | }; |
| | | return statusTypeMap[status] || 'default'; |
| | | }; |
| | | |
| | | // 获取状态文本 |
| | | const getStatusText = (status) => { |
| | | const statusTextMap = { |
| | | notStarted: '未开始', |
| | | completed: '已完成', |
| | | delayed: '已延迟' |
| | | // 提交编辑表单 |
| | | const submitEditForm = async () => { |
| | | try { |
| | | await formRef.value.validate(); |
| | | |
| | | // 发送更新请求 |
| | | const res = await updateProjectPhase(form); |
| | | |
| | | if (res.code === 200) { |
| | | ElMessage.success("里程碑编辑成功"); |
| | | dialogVisible.value = false; |
| | | getMilestoneList(); // 刷新列表 |
| | | emit("refresh"); // 通知父组件刷新 |
| | | } else { |
| | | ElMessage.error(res.msg || "里程碑编辑失败"); |
| | | } |
| | | } catch (error) { |
| | | if (error.name === "ValidationError") { |
| | | // 表单验证失败,Element Plus会自动提示 |
| | | return; |
| | | } |
| | | ElMessage.error("里程碑编辑失败"); |
| | | console.error("编辑里程碑失败:", error); |
| | | } |
| | | }; |
| | | return statusTextMap[status] || status; |
| | | }; |
| | | |
| | | // 监听项目ID变化 |
| | | watch(() => props.projectId, () => { |
| | | if (props.projectId) { |
| | | getMilestoneList(); |
| | | } |
| | | }); |
| | | // 删除里程碑 |
| | | const handleDelete = milestone => { |
| | | ElMessageBox.confirm( |
| | | `确定要删除里程碑 "${milestone.phaseName}" 吗?删除后将无法恢复。`, |
| | | "删除确认", |
| | | { |
| | | confirmButtonText: "确定", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | } |
| | | ) |
| | | .then(async () => { |
| | | try { |
| | | // 调用删除API |
| | | const res = await delProjectPhase(milestone.phaseId); |
| | | |
| | | // 初始化 |
| | | onMounted(() => { |
| | | if (props.projectId) { |
| | | getMilestoneList(); |
| | | } |
| | | }); |
| | | if (res.code === 200) { |
| | | ElMessage.success("里程碑删除成功"); |
| | | getMilestoneList(); // 刷新列表 |
| | | emit("refresh"); // 通知父组件刷新 |
| | | } else { |
| | | ElMessage.error(res.msg || "里程碑删除失败"); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error("里程碑删除失败"); |
| | | console.error("删除里程碑失败:", error); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | // 用户取消删除 |
| | | ElMessage.info("已取消删除"); |
| | | }); |
| | | }; |
| | | |
| | | // 获取状态标签类型 |
| | | const getStatusType = status => { |
| | | const statusTypeMap = { |
| | | notStarted: "info", |
| | | completed: "success", |
| | | delayed: "danger", |
| | | }; |
| | | return statusTypeMap[status] || "default"; |
| | | }; |
| | | |
| | | // 获取状态文本 |
| | | const getStatusText = status => { |
| | | const statusTextMap = { |
| | | notStarted: "未开始", |
| | | completed: "已完成", |
| | | delayed: "已延迟", |
| | | }; |
| | | return statusTextMap[status] || status; |
| | | }; |
| | | |
| | | // 监听项目ID变化 |
| | | watch( |
| | | () => props.projectId, |
| | | () => { |
| | | if (props.projectId) { |
| | | getMilestoneList(); |
| | | } |
| | | } |
| | | ); |
| | | |
| | | // 初始化 |
| | | onMounted(() => { |
| | | if (props.projectId) { |
| | | getMilestoneList(); |
| | | } |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .milestone-list-container { |
| | | padding: 10px 0; |
| | | } |
| | | .milestone-list-container { |
| | | padding: 10px 0; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .milestone-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | .milestone-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .milestone-content { |
| | | padding: 10px 0; |
| | | } |
| | | .milestone-content { |
| | | padding: 10px 0; |
| | | } |
| | | |
| | | .milestone-status { |
| | | margin-top: 10px; |
| | | } |
| | | .milestone-status { |
| | | margin-top: 10px; |
| | | } |
| | | |
| | | .empty-tip { |
| | | margin-top: 40px; |
| | | text-align: center; |
| | | } |
| | | .empty-tip { |
| | | margin-top: 40px; |
| | | text-align: center; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | </style> |
| | |
| | | <div class="task-tree-container"> |
| | | <!-- 任务树操作按钮 --> |
| | | <div class="tree-actions mb10"> |
| | | <el-button type="primary" size="small" icon="Plus" @click="handleAddTask">添加任务</el-button> |
| | | <el-button type="success" size="small" icon="RefreshRight" @click="refreshTree">刷新</el-button> |
| | | <el-button type="info" size="small" icon="Filter" @click="toggleFilter"> |
| | | <el-button type="primary" |
| | | size="small" |
| | | icon="Plus" |
| | | @click="handleAddTask">添加任务</el-button> |
| | | <el-button type="success" |
| | | size="small" |
| | | icon="RefreshRight" |
| | | @click="refreshTree">刷新</el-button> |
| | | <el-button type="info" |
| | | size="small" |
| | | icon="Filter" |
| | | @click="toggleFilter"> |
| | | {{ showFilter ? '隐藏筛选' : '显示筛选' }} |
| | | </el-button> |
| | | </div> |
| | | |
| | | <!-- 筛选条件 --> |
| | | <div v-if="showFilter" class="filter-section mb10"> |
| | | <el-form :inline="true" :model="filterParams"> |
| | | <div v-if="showFilter" |
| | | class="filter-section mb10"> |
| | | <el-form :inline="true" |
| | | :model="filterParams"> |
| | | <el-form-item label="任务状态"> |
| | | <el-select v-model="filterParams.status" placeholder="全部" clearable style="width: 120px"> |
| | | <el-option label="未开始" value="notStarted" /> |
| | | <el-option label="进行中" value="inProgress" /> |
| | | <el-option label="已完成" value="completed" /> |
| | | <el-option label="已逾期" value="overdue" /> |
| | | <el-select v-model="filterParams.status" |
| | | placeholder="全部" |
| | | clearable |
| | | style="width: 120px"> |
| | | <el-option label="未开始" |
| | | value="notStarted" /> |
| | | <el-option label="进行中" |
| | | value="inProgress" /> |
| | | <el-option label="已完成" |
| | | value="completed" /> |
| | | <el-option label="已逾期" |
| | | value="overdue" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="负责人"> |
| | | <el-input v-model="filterParams.assignee" placeholder="输入负责人" clearable style="width: 150px" /> |
| | | <el-input v-model="filterParams.assignee" |
| | | placeholder="输入负责人" |
| | | clearable |
| | | style="width: 150px" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" size="small" @click="filterTree">筛选</el-button> |
| | | <el-button size="small" @click="resetFilter">重置</el-button> |
| | | <el-button type="primary" |
| | | size="small" |
| | | @click="filterTree">筛选</el-button> |
| | | <el-button size="small" |
| | | @click="resetFilter">重置</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | |
| | | <!-- 任务树 --> |
| | | <div class="tree-content"> |
| | | <el-tree |
| | | v-loading="loading" |
| | | :data="taskTreeData" |
| | | :props="defaultProps" |
| | | :expand-on-click-node="false" |
| | | node-key="nodeId" |
| | | ref="treeRef" |
| | | @node-contextmenu="handleContextMenu" |
| | | @node-click="handleNodeClick" |
| | | > |
| | | <el-tree v-loading="loading" |
| | | :data="taskTreeData" |
| | | :props="defaultProps" |
| | | :expand-on-click-node="false" |
| | | node-key="nodeId" |
| | | ref="treeRef" |
| | | @node-contextmenu="handleContextMenu" |
| | | @node-click="handleNodeClick"> |
| | | <template #default="{ node, data }"> |
| | | <!-- 节点内容 --> |
| | | <div class="tree-node-content" :class="{ 'phase-node': data.type === 'phase', 'task-node': data.type === 'task' }"> |
| | | <div class="tree-node-content" |
| | | :class="{ 'phase-node': data.type === 'phase', 'task-node': data.type === 'task' }"> |
| | | <!-- 节点图标 --> |
| | | <div class="node-icon"> |
| | | <i v-if="data.type === 'phase'" class="el-icon-folder text-primary" /> |
| | | <i v-else-if="data.status === 'completed'" class="el-icon-circle-check text-success" /> |
| | | <i v-else-if="data.status === 'inProgress'" class="el-icon-circle-check text-primary" /> |
| | | <i v-else-if="data.status === 'overdue'" class="el-icon-alarm-clock text-danger" /> |
| | | <i v-else class="el-icon-circle-close text-gray-400" /> |
| | | <i v-if="data.type === 'phase'" |
| | | class="el-icon-folder text-primary" /> |
| | | <i v-else-if="data.status === 'completed'" |
| | | class="el-icon-circle-check text-success" /> |
| | | <i v-else-if="data.status === 'inProgress'" |
| | | class="el-icon-circle-check text-primary" /> |
| | | <i v-else-if="data.status === 'overdue'" |
| | | class="el-icon-alarm-clock text-danger" /> |
| | | <i v-else |
| | | class="el-icon-circle-close text-gray-400" /> |
| | | </div> |
| | | |
| | | <!-- 节点标题和描述 --> |
| | | <div class="node-info"> |
| | | <div class="node-title" :class="{ 'overdue-title': data.type === 'task' && data.status === 'overdue' }"> |
| | | <div class="node-title" |
| | | :class="{ 'overdue-title': data.type === 'task' && data.status === 'overdue' }"> |
| | | {{ node.label }} |
| | | <span v-if="data.type === 'task' && data.priority === 'high'" class="priority-tag">高优</span> |
| | | <span v-else-if="data.type === 'task' && data.priority === 'medium'" class="priority-tag medium">中优</span> |
| | | <span v-if="data.type === 'task' && data.priority === 'high'" |
| | | class="priority-tag">高优</span> |
| | | <span v-else-if="data.type === 'task' && data.priority === 'medium'" |
| | | class="priority-tag medium">中优</span> |
| | | </div> |
| | | <div v-if="data.description" class="node-description">{{ data.description }}</div> |
| | | |
| | | <div v-if="data.description" |
| | | class="node-description">{{ data.description }}</div> |
| | | <!-- 任务元信息 --> |
| | | <div v-if="data.type === 'task'" class="task-meta"> |
| | | <div v-if="data.type === 'task'" |
| | | class="task-meta"> |
| | | <span class="meta-item"> |
| | | <i class="el-icon-user"></i> |
| | | {{ data.assigneeName || '未分配' }} |
| | |
| | | </span> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 任务进度条 --> |
| | | <div v-if="data.type === 'task'" class="task-progress"> |
| | | <el-progress :percentage="data.progress || 0" :stroke-width="4" :show-text="false" /> |
| | | <div v-if="data.type === 'task'" |
| | | class="task-progress"> |
| | | <el-progress :percentage="data.progress || 0" |
| | | :stroke-width="4" |
| | | :show-text="false" /> |
| | | </div> |
| | | |
| | | <!-- 操作按钮 --> |
| | | <div class="node-actions"> |
| | | <el-button |
| | | v-if="data.type === 'task'" |
| | | type="text" |
| | | size="small" |
| | | icon="Edit" |
| | | @click.stop="handleEditTask(data)" |
| | | v-hasPermi="['oaSystem:task:edit']" |
| | | /> |
| | | <el-button |
| | | v-if="data.type === 'phase'" |
| | | type="text" |
| | | size="small" |
| | | icon="Plus" |
| | | @click.stop="handleAddTaskUnderPhase(data)" |
| | | v-hasPermi="['oaSystem:task:add']" |
| | | /> |
| | | <el-button |
| | | type="text" |
| | | size="small" |
| | | icon="Delete" |
| | | @click.stop="handleDeleteNode(data)" |
| | | v-hasPermi="['oaSystem:task:remove']" |
| | | /> |
| | | <el-button v-if="data.type === 'task'" |
| | | type="text" |
| | | size="small" |
| | | icon="Edit" |
| | | @click.stop="handleEditTask(data)" |
| | | v-hasPermi="['oaSystem:task:edit']" /> |
| | | <el-button v-if="data.type === 'phase'" |
| | | type="text" |
| | | size="small" |
| | | icon="Plus" |
| | | @click.stop="handleAddTaskUnderPhase(data)" |
| | | v-hasPermi="['oaSystem:task:add']" /> |
| | | <el-button type="text" |
| | | size="small" |
| | | icon="Delete" |
| | | @click.stop="handleDeleteNode(data)" |
| | | v-hasPermi="['oaSystem:task:remove']" /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </el-tree> |
| | | </div> |
| | | |
| | | <!-- 右键菜单 --> |
| | | <div v-if="showContextMenu" :style="contextMenuStyle" class="context-menu"> |
| | | <div v-if="showContextMenu" |
| | | :style="contextMenuStyle" |
| | | class="context-menu"> |
| | | <el-menu @select="handleContextMenuSelect"> |
| | | <el-menu-item v-if="selectedNode.type === 'task'" index="edit">编辑任务</el-menu-item> |
| | | <el-menu-item v-if="selectedNode.type === 'phase'" index="addTask">添加子任务</el-menu-item> |
| | | <el-menu-item v-if="selectedNode.type === 'task'" |
| | | index="edit">编辑任务</el-menu-item> |
| | | <el-menu-item v-if="selectedNode.type === 'phase'" |
| | | index="addTask">添加子任务</el-menu-item> |
| | | <el-menu-item index="delete">删除</el-menu-item> |
| | | <el-menu-item index="expandAll">展开全部</el-menu-item> |
| | | <el-menu-item index="collapseAll">收起全部</el-menu-item> |
| | | </el-menu> |
| | | </div> |
| | | |
| | | <!-- 任务表单对话框 --> |
| | | <el-dialog :title="dialogTitle" v-model="dialogOpen" width="600px" append-to-body> |
| | | <el-form ref="taskFormRef" :model="taskForm" :rules="taskRules" label-width="80px"> |
| | | <el-form-item label="任务名称" prop="taskName"> |
| | | <el-input v-model="taskForm.taskName" placeholder="请输入任务名称" /> |
| | | <el-dialog :title="dialogTitle" |
| | | v-model="dialogOpen" |
| | | width="600px" |
| | | append-to-body> |
| | | <el-form ref="taskFormRef" |
| | | :model="taskForm" |
| | | :rules="taskRules" |
| | | label-width="80px"> |
| | | <el-form-item label="任务名称" |
| | | prop="taskName"> |
| | | <el-input v-model="taskForm.taskName" |
| | | placeholder="请输入任务名称" /> |
| | | </el-form-item> |
| | | <el-form-item label="负责人" prop="assigneeId"> |
| | | <el-input v-model="taskForm.assigneeId" placeholder="请输入负责人ID" /> |
| | | <el-form-item label="负责人" |
| | | prop="assigneeId"> |
| | | <el-input v-model="taskForm.assigneeId" |
| | | placeholder="请输入负责人ID" /> |
| | | </el-form-item> |
| | | <el-form-item label="开始日期" prop="startDate"> |
| | | <el-date-picker |
| | | v-model="taskForm.startDate" |
| | | type="date" |
| | | placeholder="选择开始日期" |
| | | style="width: 100%" |
| | | /> |
| | | <el-form-item label="开始日期" |
| | | prop="startDate"> |
| | | <el-date-picker v-model="taskForm.startDate" |
| | | type="date" |
| | | placeholder="选择开始日期" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="结束日期" prop="endDate"> |
| | | <el-date-picker |
| | | v-model="taskForm.endDate" |
| | | type="date" |
| | | placeholder="选择结束日期" |
| | | style="width: 100%" |
| | | /> |
| | | <el-form-item label="结束日期" |
| | | prop="endDate"> |
| | | <el-date-picker v-model="taskForm.endDate" |
| | | type="date" |
| | | placeholder="选择结束日期" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="优先级" prop="priority"> |
| | | <el-select v-model="taskForm.priority" placeholder="选择优先级"> |
| | | <el-option label="低" value="low" /> |
| | | <el-option label="中" value="medium" /> |
| | | <el-option label="高" value="high" /> |
| | | <el-form-item label="优先级" |
| | | prop="priority"> |
| | | <el-select v-model="taskForm.priority" |
| | | placeholder="选择优先级"> |
| | | <el-option label="低" |
| | | value="low" /> |
| | | <el-option label="中" |
| | | value="medium" /> |
| | | <el-option label="高" |
| | | value="high" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="进度" prop="progress"> |
| | | <el-input-number v-model="taskForm.progress" :min="0" :max="100" style="width: 100%" /> |
| | | <el-form-item label="进度" |
| | | prop="progress"> |
| | | <el-input-number v-model="taskForm.progress" |
| | | :min="0" |
| | | :max="100" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="描述" prop="description"> |
| | | <el-input v-model="taskForm.description" type="textarea" placeholder="请输入任务描述" /> |
| | | <el-form-item label="描述" |
| | | prop="description"> |
| | | <el-input v-model="taskForm.description" |
| | | type="textarea" |
| | | placeholder="请输入任务描述" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="submitTaskForm">确定</el-button> |
| | | <el-button @click="dialogOpen = false">取消</el-button> |
| | | <el-button type="primary" @click="submitTaskForm">确定</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed, watch, onMounted } from 'vue'; |
| | | import { ElMessage, ElMessageBox, ElMenu, ElMenuItem } from 'element-plus'; |
| | | // import { getProject, addTask, updateTask, deleteTask, deletePhase } from '@/api/oaSystem/projectManagement'; |
| | | import { ref, reactive, computed, watch, onMounted } from "vue"; |
| | | import { ElMessage, ElMessageBox, ElMenu, ElMenuItem } from "element-plus"; |
| | | // import { getProject, addTask, updateTask, deleteTask, deletePhase } from '@/api/oaSystem/projectManagement'; |
| | | |
| | | const props = defineProps({ |
| | | projectId: { |
| | | type: String, |
| | | required: true |
| | | } |
| | | }); |
| | | |
| | | const emit = defineEmits(['refresh']); |
| | | |
| | | // 组件状态 |
| | | const loading = ref(false); |
| | | const treeRef = ref(); |
| | | const showContextMenu = ref(false); |
| | | const contextMenuStyle = ref({}); |
| | | const selectedNode = ref({}); |
| | | const showFilter = ref(false); |
| | | const dialogOpen = ref(false); |
| | | const dialogTitle = ref(''); |
| | | const taskFormRef = ref(); |
| | | |
| | | // 筛选参数 |
| | | const filterParams = reactive({ |
| | | status: '', |
| | | assignee: '' |
| | | }); |
| | | |
| | | // 任务表单数据 |
| | | const taskForm = reactive({ |
| | | taskId: undefined, |
| | | taskName: '', |
| | | description: '', |
| | | startDate: '', |
| | | endDate: '', |
| | | assigneeId: '', |
| | | assigneeName: '', |
| | | status: 'notStarted', |
| | | progress: 0, |
| | | priority: 'medium', |
| | | phaseId: '', |
| | | projectId: props.projectId |
| | | }); |
| | | |
| | | // 表单验证规则 |
| | | const taskRules = { |
| | | taskName: [ |
| | | { required: true, message: '任务名称不能为空', trigger: 'blur' }, |
| | | { min: 2, max: 50, message: '任务名称长度在 2 到 50 个字符', trigger: 'blur' } |
| | | ], |
| | | startDate: [ |
| | | { required: true, message: '开始日期不能为空', trigger: 'change' } |
| | | ], |
| | | endDate: [ |
| | | { required: true, message: '结束日期不能为空', trigger: 'change' } |
| | | ], |
| | | assigneeId: [ |
| | | { required: true, message: '负责人不能为空', trigger: 'blur' } |
| | | ], |
| | | progress: [ |
| | | { required: true, message: '进度不能为空', trigger: 'blur' }, |
| | | { type: 'number', min: 0, max: 100, message: '进度必须在 0 到 100 之间', trigger: 'blur' } |
| | | ] |
| | | }; |
| | | |
| | | // 任务树数据 |
| | | const rawTaskTreeData = ref([]); |
| | | |
| | | // 模拟任务数据 |
| | | const mockTaskData = { |
| | | 'PRJ2023001': [ |
| | | { |
| | | phaseId: 'PHASE001', |
| | | phaseName: '需求分析', |
| | | startDate: '2023-11-01', |
| | | endDate: '2023-11-15', |
| | | status: 'completed', |
| | | tasks: [ |
| | | { |
| | | taskId: 'TASK001', |
| | | taskName: '需求调研', |
| | | description: '调研用户需求和业务流程', |
| | | startDate: '2023-11-01', |
| | | endDate: '2023-11-05', |
| | | assigneeId: 'USER001', |
| | | assigneeName: '张三', |
| | | status: 'completed', |
| | | progress: 100, |
| | | priority: 'medium' |
| | | }, |
| | | { |
| | | taskId: 'TASK002', |
| | | taskName: '需求文档编写', |
| | | description: '编写详细的需求规格说明书', |
| | | startDate: '2023-11-06', |
| | | endDate: '2023-11-15', |
| | | assigneeId: 'USER002', |
| | | assigneeName: '李四', |
| | | status: 'completed', |
| | | progress: 100, |
| | | priority: 'high' |
| | | } |
| | | ] |
| | | const props = defineProps({ |
| | | projectId: { |
| | | type: String, |
| | | required: true, |
| | | }, |
| | | { |
| | | phaseId: 'PHASE002', |
| | | phaseName: '系统设计', |
| | | startDate: '2023-11-16', |
| | | endDate: '2023-12-10', |
| | | status: 'completed', |
| | | tasks: [ |
| | | { |
| | | taskId: 'TASK003', |
| | | taskName: '系统架构设计', |
| | | description: '设计系统整体架构', |
| | | startDate: '2023-11-16', |
| | | endDate: '2023-11-25', |
| | | assigneeId: 'USER003', |
| | | assigneeName: '王五', |
| | | status: 'completed', |
| | | progress: 100, |
| | | priority: 'high' |
| | | }, |
| | | { |
| | | taskId: 'TASK004', |
| | | taskName: '数据库设计', |
| | | description: '设计数据库表结构和关系', |
| | | startDate: '2023-11-26', |
| | | endDate: '2023-12-10', |
| | | assigneeId: 'USER004', |
| | | assigneeName: '赵六', |
| | | status: 'completed', |
| | | progress: 100, |
| | | priority: 'medium' |
| | | } |
| | | ] |
| | | }, |
| | | { |
| | | phaseId: 'PHASE003', |
| | | phaseName: '开发实现', |
| | | startDate: '2023-12-11', |
| | | endDate: '2024-01-31', |
| | | status: 'inProgress', |
| | | tasks: [ |
| | | { |
| | | taskId: 'TASK005', |
| | | taskName: '前端开发', |
| | | description: '开发用户界面和交互逻辑', |
| | | startDate: '2023-12-11', |
| | | endDate: '2024-01-15', |
| | | assigneeId: 'USER005', |
| | | assigneeName: '钱七', |
| | | status: 'inProgress', |
| | | progress: 70, |
| | | priority: 'high' |
| | | }, |
| | | { |
| | | taskId: 'TASK006', |
| | | taskName: '后端开发', |
| | | description: '开发业务逻辑和API接口', |
| | | startDate: '2023-12-11', |
| | | endDate: '2024-01-20', |
| | | assigneeId: 'USER006', |
| | | assigneeName: '孙八', |
| | | status: 'inProgress', |
| | | progress: 60, |
| | | priority: 'high' |
| | | } |
| | | ] |
| | | } |
| | | ], |
| | | // 默认数据 |
| | | default: [ |
| | | { |
| | | phaseId: 'PHASE_DEFAULT1', |
| | | phaseName: '准备阶段', |
| | | startDate: '2023-01-01', |
| | | endDate: '2023-03-31', |
| | | status: 'completed', |
| | | tasks: [ |
| | | { |
| | | taskId: 'TASK_DEFAULT1', |
| | | taskName: '项目启动', |
| | | description: '召开项目启动会议', |
| | | startDate: '2023-01-01', |
| | | endDate: '2023-01-05', |
| | | assigneeId: 'USER_DEFAULT1', |
| | | assigneeName: '负责人A', |
| | | status: 'completed', |
| | | progress: 100, |
| | | priority: 'high' |
| | | } |
| | | ] |
| | | }, |
| | | { |
| | | phaseId: 'PHASE_DEFAULT2', |
| | | phaseName: '执行阶段', |
| | | startDate: '2023-04-01', |
| | | endDate: '2023-09-30', |
| | | status: 'inProgress', |
| | | tasks: [ |
| | | { |
| | | taskId: 'TASK_DEFAULT2', |
| | | taskName: '核心功能开发', |
| | | description: '开发系统核心功能模块', |
| | | startDate: '2023-04-01', |
| | | endDate: '2023-06-30', |
| | | assigneeId: 'USER_DEFAULT2', |
| | | assigneeName: '负责人B', |
| | | status: 'inProgress', |
| | | progress: 50, |
| | | priority: 'high' |
| | | } |
| | | ] |
| | | } |
| | | ] |
| | | }; |
| | | |
| | | const taskTreeData = computed(() => { |
| | | // 应用筛选条件 |
| | | if (!showFilter.value || (!filterParams.status && !filterParams.assignee)) { |
| | | return rawTaskTreeData.value; |
| | | } |
| | | |
| | | // 深拷贝原始数据以避免修改 |
| | | const filteredData = JSON.parse(JSON.stringify(rawTaskTreeData.value)); |
| | | |
| | | // 递归筛选节点 |
| | | const filterNodes = (nodes) => { |
| | | const result = []; |
| | | |
| | | nodes.forEach(node => { |
| | | // 对于阶段节点,检查其子任务是否符合筛选条件 |
| | | if (node.type === 'phase' && node.children) { |
| | | const filteredChildren = filterNodes(node.children); |
| | | if (filteredChildren.length > 0) { |
| | | // 保留至少有一个子任务符合条件的阶段 |
| | | node.children = filteredChildren; |
| | | result.push(node); |
| | | } |
| | | } |
| | | // 对于任务节点,直接应用筛选条件 |
| | | else if (node.type === 'task') { |
| | | const statusMatch = !filterParams.status || node.status === filterParams.status; |
| | | const assigneeMatch = !filterParams.assignee || |
| | | (node.assigneeName && node.assigneeName.includes(filterParams.assignee)); |
| | | |
| | | if (statusMatch && assigneeMatch) { |
| | | result.push(node); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | return result; |
| | | }; |
| | | |
| | | return filterNodes(filteredData); |
| | | }); |
| | | |
| | | // 树节点配置 |
| | | const defaultProps = { |
| | | children: 'children', |
| | | label: (data) => { |
| | | if (data.type === 'phase') { |
| | | return `${data.phaseName}`; |
| | | } else { |
| | | return `${data.taskName}`; |
| | | } |
| | | } |
| | | }; |
| | | |
| | | // 加载任务树数据 |
| | | const loadTaskTree = async () => { |
| | | loading.value = true; |
| | | // try { |
| | | // const { data } = await getProject(props.projectId); |
| | | // rawTaskTreeData.value = buildTaskTree(data.phases || []); |
| | | // } catch (error) { |
| | | // ElMessage.error('加载任务树失败'); |
| | | // console.error('加载任务树失败:', error); |
| | | // } finally { |
| | | // loading.value = false; |
| | | // } |
| | | try { |
| | | // 模拟网络延迟 |
| | | await new Promise(resolve => setTimeout(resolve, 500)); |
| | | |
| | | // 使用模拟数据替代API请求 |
| | | const phases = mockTaskData[props.projectId] || mockTaskData.default; |
| | | rawTaskTreeData.value = buildTaskTree(phases); |
| | | } catch (error) { |
| | | ElMessage.error('加载任务树失败'); |
| | | console.error('加载任务树失败:', error); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | |
| | | // 构建任务树 |
| | | const buildTaskTree = (phases) => { |
| | | return phases.map(phase => ({ |
| | | nodeId: phase.phaseId, |
| | | phaseId: phase.phaseId, |
| | | phaseName: phase.phaseName, |
| | | type: 'phase', |
| | | children: (phase.tasks || []).map(task => ({ |
| | | nodeId: task.taskId, |
| | | taskId: task.taskId, |
| | | taskName: task.taskName, |
| | | description: task.description, |
| | | startDate: task.startDate, |
| | | endDate: task.endDate, |
| | | assigneeId: task.assigneeId, |
| | | assigneeName: task.assigneeName, |
| | | status: task.status, |
| | | progress: task.progress, |
| | | priority: task.priority, |
| | | phaseId: task.phaseId, |
| | | projectId: props.projectId, |
| | | type: 'task' |
| | | })) |
| | | })); |
| | | }; |
| | | |
| | | // 格式化日期范围 |
| | | const formatDateRange = (startDate, endDate) => { |
| | | if (!startDate || !endDate) return ''; |
| | | return `${startDate} - ${endDate}`; |
| | | }; |
| | | |
| | | // 刷新树 |
| | | const refreshTree = () => { |
| | | loadTaskTree(); |
| | | // 通知父组件刷新数据 |
| | | emit('refresh'); |
| | | }; |
| | | |
| | | // 切换筛选面板 |
| | | const toggleFilter = () => { |
| | | showFilter.value = !showFilter.value; |
| | | }; |
| | | |
| | | // 应用筛选 |
| | | const filterTree = () => { |
| | | // 筛选逻辑已经在computed中实现 |
| | | }; |
| | | |
| | | // 重置筛选 |
| | | const resetFilter = () => { |
| | | filterParams.status = ''; |
| | | filterParams.assignee = ''; |
| | | }; |
| | | |
| | | // 处理节点点击 |
| | | const handleNodeClick = (data, node) => { |
| | | // 切换展开/收起状态 |
| | | if (data.type === 'phase') { |
| | | node.expanded = !node.expanded; |
| | | } |
| | | }; |
| | | |
| | | // 处理右键菜单 |
| | | const handleContextMenu = (event, data) => { |
| | | event.preventDefault(); |
| | | selectedNode.value = data; |
| | | contextMenuStyle.value = { |
| | | position: 'fixed', |
| | | left: `${event.clientX}px`, |
| | | top: `${event.clientY}px`, |
| | | zIndex: 1000 |
| | | }; |
| | | showContextMenu.value = true; |
| | | }; |
| | | |
| | | // 处理右键菜单选择 |
| | | const handleContextMenuSelect = (index) => { |
| | | showContextMenu.value = false; |
| | | switch (index) { |
| | | case 'edit': |
| | | if (selectedNode.value.type === 'task') { |
| | | handleEditTask(selectedNode.value); |
| | | } |
| | | break; |
| | | case 'addTask': |
| | | if (selectedNode.value.type === 'phase') { |
| | | handleAddTaskUnderPhase(selectedNode.value); |
| | | } |
| | | break; |
| | | case 'delete': |
| | | handleDeleteNode(selectedNode.value); |
| | | break; |
| | | case 'expandAll': |
| | | treeRef.value?.expandAll(); |
| | | break; |
| | | case 'collapseAll': |
| | | treeRef.value?.collapseAll(); |
| | | break; |
| | | } |
| | | }; |
| | | |
| | | // 添加任务 |
| | | const handleAddTask = () => { |
| | | resetTaskForm(); |
| | | dialogTitle.value = '添加任务'; |
| | | dialogOpen.value = true; |
| | | }; |
| | | |
| | | // 在指定阶段下添加任务 |
| | | const handleAddTaskUnderPhase = (phase) => { |
| | | resetTaskForm(); |
| | | taskForm.phaseId = phase.phaseId; |
| | | dialogTitle.value = '添加子任务'; |
| | | dialogOpen.value = true; |
| | | }; |
| | | |
| | | // 编辑任务 |
| | | const handleEditTask = (task) => { |
| | | resetTaskForm(); |
| | | Object.assign(taskForm, { ...task }); |
| | | dialogTitle.value = '编辑任务'; |
| | | dialogOpen.value = true; |
| | | }; |
| | | |
| | | // 删除节点 |
| | | const handleDeleteNode = async (node) => { |
| | | const confirmMessage = node.type === 'phase' |
| | | ? `确定要删除阶段 "${node.phaseName}" 及其所有子任务吗?` |
| | | : `确定要删除任务 "${node.taskName}" 吗?`; |
| | | |
| | | await ElMessageBox.confirm(confirmMessage, '确认操作', { |
| | | confirmButtonText: '确定', |
| | | cancelButtonText: '取消', |
| | | type: 'warning' |
| | | }).catch(() => { |
| | | throw new Error('取消删除'); |
| | | }); |
| | | |
| | | try { |
| | | if (node.type === 'phase') { |
| | | await deletePhase(node.phaseId); |
| | | } else { |
| | | await deleteTask(node.taskId); |
| | | |
| | | const emit = defineEmits(["refresh"]); |
| | | |
| | | // 组件状态 |
| | | const loading = ref(false); |
| | | const treeRef = ref(); |
| | | const showContextMenu = ref(false); |
| | | const contextMenuStyle = ref({}); |
| | | const selectedNode = ref({}); |
| | | const showFilter = ref(false); |
| | | const dialogOpen = ref(false); |
| | | const dialogTitle = ref(""); |
| | | const taskFormRef = ref(); |
| | | |
| | | // 筛选参数 |
| | | const filterParams = reactive({ |
| | | status: "", |
| | | assignee: "", |
| | | }); |
| | | |
| | | // 任务表单数据 |
| | | const taskForm = reactive({ |
| | | taskId: undefined, |
| | | taskName: "", |
| | | description: "", |
| | | startDate: "", |
| | | endDate: "", |
| | | assigneeId: "", |
| | | assigneeName: "", |
| | | status: "notStarted", |
| | | progress: 0, |
| | | priority: "medium", |
| | | phaseId: "", |
| | | projectId: props.projectId, |
| | | }); |
| | | |
| | | // 表单验证规则 |
| | | const taskRules = { |
| | | taskName: [ |
| | | { required: true, message: "任务名称不能为空", trigger: "blur" }, |
| | | { |
| | | min: 2, |
| | | max: 50, |
| | | message: "任务名称长度在 2 到 50 个字符", |
| | | trigger: "blur", |
| | | }, |
| | | ], |
| | | startDate: [ |
| | | { required: true, message: "开始日期不能为空", trigger: "change" }, |
| | | ], |
| | | endDate: [{ required: true, message: "结束日期不能为空", trigger: "change" }], |
| | | assigneeId: [{ required: true, message: "负责人不能为空", trigger: "blur" }], |
| | | progress: [ |
| | | { required: true, message: "进度不能为空", trigger: "blur" }, |
| | | { |
| | | type: "number", |
| | | min: 0, |
| | | max: 100, |
| | | message: "进度必须在 0 到 100 之间", |
| | | trigger: "blur", |
| | | }, |
| | | ], |
| | | }; |
| | | |
| | | // 任务树数据 |
| | | const rawTaskTreeData = ref([]); |
| | | |
| | | // 模拟任务数据 |
| | | const mockTaskData = { |
| | | PRJ2023001: [ |
| | | { |
| | | phaseId: "PHASE001", |
| | | phaseName: "需求分析", |
| | | startDate: "2023-11-01", |
| | | endDate: "2023-11-15", |
| | | status: "completed", |
| | | tasks: [ |
| | | { |
| | | taskId: "TASK001", |
| | | taskName: "需求调研", |
| | | description: "调研用户需求和业务流程", |
| | | startDate: "2023-11-01", |
| | | endDate: "2023-11-05", |
| | | assigneeId: "USER001", |
| | | assigneeName: "张三", |
| | | status: "completed", |
| | | progress: 100, |
| | | priority: "medium", |
| | | }, |
| | | { |
| | | taskId: "TASK002", |
| | | taskName: "需求文档编写", |
| | | description: "编写详细的需求规格说明书", |
| | | startDate: "2023-11-06", |
| | | endDate: "2023-11-15", |
| | | assigneeId: "USER002", |
| | | assigneeName: "李四", |
| | | status: "completed", |
| | | progress: 100, |
| | | priority: "high", |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | phaseId: "PHASE002", |
| | | phaseName: "系统设计", |
| | | startDate: "2023-11-16", |
| | | endDate: "2023-12-10", |
| | | status: "completed", |
| | | tasks: [ |
| | | { |
| | | taskId: "TASK003", |
| | | taskName: "系统架构设计", |
| | | description: "设计系统整体架构", |
| | | startDate: "2023-11-16", |
| | | endDate: "2023-11-25", |
| | | assigneeId: "USER003", |
| | | assigneeName: "王五", |
| | | status: "completed", |
| | | progress: 100, |
| | | priority: "high", |
| | | }, |
| | | { |
| | | taskId: "TASK004", |
| | | taskName: "数据库设计", |
| | | description: "设计数据库表结构和关系", |
| | | startDate: "2023-11-26", |
| | | endDate: "2023-12-10", |
| | | assigneeId: "USER004", |
| | | assigneeName: "赵六", |
| | | status: "completed", |
| | | progress: 100, |
| | | priority: "medium", |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | phaseId: "PHASE003", |
| | | phaseName: "开发实现", |
| | | startDate: "2023-12-11", |
| | | endDate: "2024-01-31", |
| | | status: "inProgress", |
| | | tasks: [ |
| | | { |
| | | taskId: "TASK005", |
| | | taskName: "前端开发", |
| | | description: "开发用户界面和交互逻辑", |
| | | startDate: "2023-12-11", |
| | | endDate: "2024-01-15", |
| | | assigneeId: "USER005", |
| | | assigneeName: "钱七", |
| | | status: "inProgress", |
| | | progress: 70, |
| | | priority: "high", |
| | | }, |
| | | { |
| | | taskId: "TASK006", |
| | | taskName: "后端开发", |
| | | description: "开发业务逻辑和API接口", |
| | | startDate: "2023-12-11", |
| | | endDate: "2024-01-20", |
| | | assigneeId: "USER006", |
| | | assigneeName: "孙八", |
| | | status: "inProgress", |
| | | progress: 60, |
| | | priority: "high", |
| | | }, |
| | | ], |
| | | }, |
| | | ], |
| | | // 默认数据 |
| | | default: [ |
| | | { |
| | | phaseId: "PHASE_DEFAULT1", |
| | | phaseName: "准备阶段", |
| | | startDate: "2023-01-01", |
| | | endDate: "2023-03-31", |
| | | status: "completed", |
| | | tasks: [ |
| | | { |
| | | taskId: "TASK_DEFAULT1", |
| | | taskName: "项目启动", |
| | | description: "召开项目启动会议", |
| | | startDate: "2023-01-01", |
| | | endDate: "2023-01-05", |
| | | assigneeId: "USER_DEFAULT1", |
| | | assigneeName: "负责人A", |
| | | status: "completed", |
| | | progress: 100, |
| | | priority: "high", |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | phaseId: "PHASE_DEFAULT2", |
| | | phaseName: "执行阶段", |
| | | startDate: "2023-04-01", |
| | | endDate: "2023-09-30", |
| | | status: "inProgress", |
| | | tasks: [ |
| | | { |
| | | taskId: "TASK_DEFAULT2", |
| | | taskName: "核心功能开发", |
| | | description: "开发系统核心功能模块", |
| | | startDate: "2023-04-01", |
| | | endDate: "2023-06-30", |
| | | assigneeId: "USER_DEFAULT2", |
| | | assigneeName: "负责人B", |
| | | status: "inProgress", |
| | | progress: 50, |
| | | priority: "high", |
| | | }, |
| | | ], |
| | | }, |
| | | ], |
| | | }; |
| | | |
| | | const taskTreeData = computed(() => { |
| | | // 应用筛选条件 |
| | | if (!showFilter.value || (!filterParams.status && !filterParams.assignee)) { |
| | | return rawTaskTreeData.value; |
| | | } |
| | | ElMessage.success('删除成功'); |
| | | refreshTree(); |
| | | } catch (error) { |
| | | if (error.message !== '取消删除') { |
| | | ElMessage.error('删除失败'); |
| | | console.error('删除失败:', error); |
| | | |
| | | // 深拷贝原始数据以避免修改 |
| | | const filteredData = JSON.parse(JSON.stringify(rawTaskTreeData.value)); |
| | | |
| | | // 递归筛选节点 |
| | | const filterNodes = nodes => { |
| | | const result = []; |
| | | |
| | | nodes.forEach(node => { |
| | | // 对于阶段节点,检查其子任务是否符合筛选条件 |
| | | if (node.type === "phase" && node.children) { |
| | | const filteredChildren = filterNodes(node.children); |
| | | if (filteredChildren.length > 0) { |
| | | // 保留至少有一个子任务符合条件的阶段 |
| | | node.children = filteredChildren; |
| | | result.push(node); |
| | | } |
| | | } |
| | | // 对于任务节点,直接应用筛选条件 |
| | | else if (node.type === "task") { |
| | | const statusMatch = |
| | | !filterParams.status || node.status === filterParams.status; |
| | | const assigneeMatch = |
| | | !filterParams.assignee || |
| | | (node.assigneeName && |
| | | node.assigneeName.includes(filterParams.assignee)); |
| | | |
| | | if (statusMatch && assigneeMatch) { |
| | | result.push(node); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | return result; |
| | | }; |
| | | |
| | | return filterNodes(filteredData); |
| | | }); |
| | | |
| | | // 树节点配置 |
| | | const defaultProps = { |
| | | children: "children", |
| | | label: data => { |
| | | if (data.type === "phase") { |
| | | return `${data.phaseName}`; |
| | | } else { |
| | | return `${data.taskName}`; |
| | | } |
| | | }, |
| | | }; |
| | | |
| | | // 加载任务树数据 |
| | | const loadTaskTree = async () => { |
| | | loading.value = true; |
| | | // try { |
| | | // const { data } = await getProject(props.projectId); |
| | | // rawTaskTreeData.value = buildTaskTree(data.phases || []); |
| | | // } catch (error) { |
| | | // ElMessage.error('加载任务树失败'); |
| | | // console.error('加载任务树失败:', error); |
| | | // } finally { |
| | | // loading.value = false; |
| | | // } |
| | | try { |
| | | // 模拟网络延迟 |
| | | await new Promise(resolve => setTimeout(resolve, 500)); |
| | | |
| | | // 使用模拟数据替代API请求 |
| | | const phases = mockTaskData[props.projectId] || mockTaskData.default; |
| | | rawTaskTreeData.value = buildTaskTree(phases); |
| | | } catch (error) { |
| | | ElMessage.error("加载任务树失败"); |
| | | console.error("加载任务树失败:", error); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | } |
| | | }; |
| | | }; |
| | | |
| | | // 重置任务表单 |
| | | const resetTaskForm = () => { |
| | | taskForm.taskId = undefined; |
| | | taskForm.taskName = ''; |
| | | taskForm.description = ''; |
| | | taskForm.startDate = ''; |
| | | taskForm.endDate = ''; |
| | | taskForm.assigneeId = ''; |
| | | taskForm.assigneeName = ''; |
| | | taskForm.status = 'notStarted'; |
| | | taskForm.progress = 0; |
| | | taskForm.priority = 'medium'; |
| | | taskForm.phaseId = ''; |
| | | taskForm.projectId = props.projectId; |
| | | |
| | | if (taskFormRef.value) { |
| | | taskFormRef.value.resetFields(); |
| | | } |
| | | }; |
| | | // 构建任务树 |
| | | const buildTaskTree = phases => { |
| | | return phases.map(phase => ({ |
| | | nodeId: phase.phaseId, |
| | | phaseId: phase.phaseId, |
| | | phaseName: phase.phaseName, |
| | | type: "phase", |
| | | children: (phase.tasks || []).map(task => ({ |
| | | nodeId: task.taskId, |
| | | taskId: task.taskId, |
| | | taskName: task.taskName, |
| | | description: task.description, |
| | | startDate: task.startDate, |
| | | endDate: task.endDate, |
| | | assigneeId: task.assigneeId, |
| | | assigneeName: task.assigneeName, |
| | | status: task.status, |
| | | progress: task.progress, |
| | | priority: task.priority, |
| | | phaseId: task.phaseId, |
| | | projectId: props.projectId, |
| | | type: "task", |
| | | })), |
| | | })); |
| | | }; |
| | | |
| | | // 提交任务表单 |
| | | const submitTaskForm = async () => { |
| | | try { |
| | | await taskFormRef.value.validate(); |
| | | |
| | | if (taskForm.taskId) { |
| | | await updateTask(taskForm); |
| | | ElMessage.success('修改任务成功'); |
| | | } else { |
| | | await addTask(taskForm); |
| | | ElMessage.success('添加任务成功'); |
| | | } |
| | | |
| | | dialogOpen.value = false; |
| | | refreshTree(); |
| | | } catch (error) { |
| | | console.error('提交表单失败:', error); |
| | | } |
| | | }; |
| | | // 格式化日期范围 |
| | | const formatDateRange = (startDate, endDate) => { |
| | | if (!startDate || !endDate) return ""; |
| | | return `${startDate} - ${endDate}`; |
| | | }; |
| | | |
| | | // 点击其他区域关闭右键菜单 |
| | | document.addEventListener('click', () => { |
| | | if (showContextMenu.value) { |
| | | showContextMenu.value = false; |
| | | } |
| | | }); |
| | | |
| | | // 监听项目ID变化 |
| | | watch(() => props.projectId, (newProjectId) => { |
| | | if (newProjectId) { |
| | | // 刷新树 |
| | | const refreshTree = () => { |
| | | loadTaskTree(); |
| | | } |
| | | }); |
| | | // 通知父组件刷新数据 |
| | | emit("refresh"); |
| | | }; |
| | | |
| | | // 初始化 |
| | | onMounted(() => { |
| | | loadTaskTree(); |
| | | }); |
| | | // 切换筛选面板 |
| | | const toggleFilter = () => { |
| | | showFilter.value = !showFilter.value; |
| | | }; |
| | | |
| | | // 应用筛选 |
| | | const filterTree = () => { |
| | | // 筛选逻辑已经在computed中实现 |
| | | }; |
| | | |
| | | // 重置筛选 |
| | | const resetFilter = () => { |
| | | filterParams.status = ""; |
| | | filterParams.assignee = ""; |
| | | }; |
| | | |
| | | // 处理节点点击 |
| | | const handleNodeClick = (data, node) => { |
| | | // 切换展开/收起状态 |
| | | if (data.type === "phase") { |
| | | node.expanded = !node.expanded; |
| | | } |
| | | }; |
| | | |
| | | // 处理右键菜单 |
| | | const handleContextMenu = (event, data) => { |
| | | event.preventDefault(); |
| | | selectedNode.value = data; |
| | | contextMenuStyle.value = { |
| | | position: "fixed", |
| | | left: `${event.clientX}px`, |
| | | top: `${event.clientY}px`, |
| | | zIndex: 1000, |
| | | }; |
| | | showContextMenu.value = true; |
| | | }; |
| | | |
| | | // 处理右键菜单选择 |
| | | const handleContextMenuSelect = index => { |
| | | showContextMenu.value = false; |
| | | switch (index) { |
| | | case "edit": |
| | | if (selectedNode.value.type === "task") { |
| | | handleEditTask(selectedNode.value); |
| | | } |
| | | break; |
| | | case "addTask": |
| | | if (selectedNode.value.type === "phase") { |
| | | handleAddTaskUnderPhase(selectedNode.value); |
| | | } |
| | | break; |
| | | case "delete": |
| | | handleDeleteNode(selectedNode.value); |
| | | break; |
| | | case "expandAll": |
| | | treeRef.value?.expandAll(); |
| | | break; |
| | | case "collapseAll": |
| | | treeRef.value?.collapseAll(); |
| | | break; |
| | | } |
| | | }; |
| | | |
| | | // 添加任务 |
| | | const handleAddTask = () => { |
| | | resetTaskForm(); |
| | | dialogTitle.value = "添加任务"; |
| | | dialogOpen.value = true; |
| | | }; |
| | | |
| | | // 在指定阶段下添加任务 |
| | | const handleAddTaskUnderPhase = phase => { |
| | | resetTaskForm(); |
| | | taskForm.phaseId = phase.phaseId; |
| | | dialogTitle.value = "添加子任务"; |
| | | dialogOpen.value = true; |
| | | }; |
| | | |
| | | // 编辑任务 |
| | | const handleEditTask = task => { |
| | | resetTaskForm(); |
| | | Object.assign(taskForm, { ...task }); |
| | | dialogTitle.value = "编辑任务"; |
| | | dialogOpen.value = true; |
| | | }; |
| | | |
| | | // 删除节点 |
| | | const handleDeleteNode = async node => { |
| | | const confirmMessage = |
| | | node.type === "phase" |
| | | ? `确定要删除阶段 "${node.phaseName}" 及其所有子任务吗?` |
| | | : `确定要删除任务 "${node.taskName}" 吗?`; |
| | | |
| | | await ElMessageBox.confirm(confirmMessage, "确认操作", { |
| | | confirmButtonText: "确定", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }).catch(() => { |
| | | throw new Error("取消删除"); |
| | | }); |
| | | |
| | | try { |
| | | if (node.type === "phase") { |
| | | await deletePhase(node.phaseId); |
| | | } else { |
| | | await deleteTask(node.taskId); |
| | | } |
| | | ElMessage.success("删除成功"); |
| | | refreshTree(); |
| | | } catch (error) { |
| | | if (error.message !== "取消删除") { |
| | | ElMessage.error("删除失败"); |
| | | console.error("删除失败:", error); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | // 重置任务表单 |
| | | const resetTaskForm = () => { |
| | | taskForm.taskId = undefined; |
| | | taskForm.taskName = ""; |
| | | taskForm.description = ""; |
| | | taskForm.startDate = ""; |
| | | taskForm.endDate = ""; |
| | | taskForm.assigneeId = ""; |
| | | taskForm.assigneeName = ""; |
| | | taskForm.status = "notStarted"; |
| | | taskForm.progress = 0; |
| | | taskForm.priority = "medium"; |
| | | taskForm.phaseId = ""; |
| | | taskForm.projectId = props.projectId; |
| | | |
| | | if (taskFormRef.value) { |
| | | taskFormRef.value.resetFields(); |
| | | } |
| | | }; |
| | | |
| | | // 提交任务表单 |
| | | const submitTaskForm = async () => { |
| | | try { |
| | | await taskFormRef.value.validate(); |
| | | |
| | | if (taskForm.taskId) { |
| | | await updateTask(taskForm); |
| | | ElMessage.success("修改任务成功"); |
| | | } else { |
| | | await addTask(taskForm); |
| | | ElMessage.success("添加任务成功"); |
| | | } |
| | | |
| | | dialogOpen.value = false; |
| | | refreshTree(); |
| | | } catch (error) { |
| | | console.error("提交表单失败:", error); |
| | | } |
| | | }; |
| | | |
| | | // 点击其他区域关闭右键菜单 |
| | | document.addEventListener("click", () => { |
| | | if (showContextMenu.value) { |
| | | showContextMenu.value = false; |
| | | } |
| | | }); |
| | | |
| | | // 监听项目ID变化 |
| | | watch( |
| | | () => props.projectId, |
| | | newProjectId => { |
| | | if (newProjectId) { |
| | | loadTaskTree(); |
| | | } |
| | | } |
| | | ); |
| | | |
| | | // 初始化 |
| | | onMounted(() => { |
| | | loadTaskTree(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .task-tree-container { |
| | | padding: 10px; |
| | | } |
| | | .task-tree-container { |
| | | padding: 10px; |
| | | } |
| | | |
| | | .tree-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | align-items: center; |
| | | } |
| | | .tree-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | align-items: center; |
| | | } |
| | | |
| | | .filter-section { |
| | | background: #f5f7fa; |
| | | padding: 10px; |
| | | border-radius: 4px; |
| | | } |
| | | .filter-section { |
| | | background: #f5f7fa; |
| | | padding: 10px; |
| | | border-radius: 4px; |
| | | } |
| | | |
| | | .tree-content { |
| | | background: #fff; |
| | | border: 1px solid #ebeef5; |
| | | border-radius: 4px; |
| | | padding: 10px; |
| | | max-height: 600px; |
| | | overflow-y: auto; |
| | | } |
| | | .tree-content { |
| | | background: #fff; |
| | | border: 1px solid #ebeef5; |
| | | border-radius: 4px; |
| | | padding: 10px; |
| | | max-height: 600px; |
| | | overflow-y: auto; |
| | | } |
| | | |
| | | .tree-node-content { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | padding: 5px 0; |
| | | min-height: 40px; |
| | | } |
| | | .tree-node-content { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | padding: 5px 0; |
| | | min-height: 40px; |
| | | } |
| | | |
| | | .phase-node { |
| | | font-weight: bold; |
| | | color: #409eff; |
| | | } |
| | | .phase-node { |
| | | font-weight: bold; |
| | | color: #409eff; |
| | | } |
| | | |
| | | .task-node { |
| | | color: #606266; |
| | | } |
| | | .task-node { |
| | | color: #606266; |
| | | } |
| | | |
| | | .node-icon { |
| | | display: flex; |
| | | align-items: center; |
| | | width: 20px; |
| | | } |
| | | .node-icon { |
| | | display: flex; |
| | | align-items: center; |
| | | width: 20px; |
| | | } |
| | | |
| | | .node-info { |
| | | flex: 1; |
| | | min-width: 0; |
| | | } |
| | | .node-info { |
| | | flex: 1; |
| | | min-width: 0; |
| | | } |
| | | |
| | | .node-title { |
| | | font-weight: 500; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | margin-bottom: 2px; |
| | | } |
| | | .node-title { |
| | | font-weight: 500; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | margin-bottom: 2px; |
| | | } |
| | | |
| | | .overdue-title { |
| | | color: #f56c6c; |
| | | font-weight: bold; |
| | | } |
| | | .overdue-title { |
| | | color: #f56c6c; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .priority-tag { |
| | | background: #f56c6c; |
| | | color: white; |
| | | font-size: 10px; |
| | | padding: 1px 4px; |
| | | border-radius: 2px; |
| | | margin-left: 5px; |
| | | } |
| | | .priority-tag { |
| | | background: #f56c6c; |
| | | color: white; |
| | | font-size: 10px; |
| | | padding: 1px 4px; |
| | | border-radius: 2px; |
| | | margin-left: 5px; |
| | | } |
| | | |
| | | .priority-tag.medium { |
| | | background: #e6a23c; |
| | | } |
| | | .priority-tag.medium { |
| | | background: #e6a23c; |
| | | } |
| | | |
| | | .node-description { |
| | | font-size: 12px; |
| | | color: #909399; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | } |
| | | .node-description { |
| | | font-size: 12px; |
| | | color: #909399; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | } |
| | | |
| | | .task-meta { |
| | | display: flex; |
| | | gap: 15px; |
| | | font-size: 12px; |
| | | color: #909399; |
| | | margin-top: 2px; |
| | | } |
| | | .task-meta { |
| | | display: flex; |
| | | gap: 15px; |
| | | font-size: 12px; |
| | | color: #909399; |
| | | margin-top: 2px; |
| | | } |
| | | |
| | | .meta-item { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 3px; |
| | | } |
| | | .meta-item { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 3px; |
| | | } |
| | | |
| | | .task-progress { |
| | | width: 120px; |
| | | margin: 0 10px; |
| | | } |
| | | .task-progress { |
| | | width: 120px; |
| | | margin: 0 10px; |
| | | } |
| | | |
| | | .node-actions { |
| | | display: flex; |
| | | gap: 5px; |
| | | opacity: 0; |
| | | transition: opacity 0.3s; |
| | | } |
| | | .node-actions { |
| | | display: flex; |
| | | gap: 5px; |
| | | opacity: 0; |
| | | transition: opacity 0.3s; |
| | | } |
| | | |
| | | .tree-node-content:hover .node-actions { |
| | | opacity: 1; |
| | | } |
| | | .tree-node-content:hover .node-actions { |
| | | opacity: 1; |
| | | } |
| | | |
| | | .context-menu { |
| | | background: white; |
| | | border: 1px solid #ebeef5; |
| | | box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
| | | border-radius: 4px; |
| | | } |
| | | .context-menu { |
| | | background: white; |
| | | border: 1px solid #ebeef5; |
| | | box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
| | | border-radius: 4px; |
| | | } |
| | | |
| | | .context-menu .el-menu { |
| | | min-width: 120px; |
| | | border: none; |
| | | } |
| | | .context-menu .el-menu { |
| | | min-width: 120px; |
| | | border: none; |
| | | } |
| | | |
| | | .context-menu .el-menu-item { |
| | | padding: 0 15px; |
| | | height: 36px; |
| | | line-height: 36px; |
| | | } |
| | | .context-menu .el-menu-item { |
| | | padding: 0 15px; |
| | | height: 36px; |
| | | line-height: 36px; |
| | | } |
| | | |
| | | .context-menu .el-menu-item:hover { |
| | | background-color: #f5f7fa; |
| | | } |
| | | .context-menu .el-menu-item:hover { |
| | | background-color: #f5f7fa; |
| | | } |
| | | |
| | | .text-gray-400 { |
| | | color: #c0c4cc; |
| | | } |
| | | .text-gray-400 { |
| | | color: #c0c4cc; |
| | | } |
| | | </style> |
| src/views/oaSystem/projectManagement/index.vue
src/views/oaSystem/projectManagement/projectDetail.vue
src/views/procurementManagement/procurementPlan/index.vue
src/views/productManagement/productIdentifier/index.vue
src/views/productionManagement/productStructure/StructureEdit.vue
src/views/productionManagement/workOrder/index.vue
src/views/salesManagement/customerManagement/index.vue
src/views/salesManagement/orderManagement/index.vue
src/views/salesManagement/paymentShipping/index.vue
src/views/salesManagement/salesQuotation/index.vue
src/views/salesManagement/salespersonManagement/index.vue
src/views/tool/gen/importTable.vue |