fix: 新增协同办公、薪资功能。完成库存预警页面编写
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | export function getMeetingRoomList(data) { |
| | | return request({ |
| | | url: "/meeting/roomList", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | export function saveRoom(data) { |
| | | return request({ |
| | | url: "/meeting/saveRoom", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | export function delRoom(id) { |
| | | return request({ |
| | | url: "/meeting/delRoom/"+id, |
| | | method: "delete", |
| | | }); |
| | | } |
| | | |
| | | export function getRoomEnum() { |
| | | return request({ |
| | | url: "/meeting/roomEnum", |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | export function getDraftList(data){ |
| | | return request({ |
| | | url: "/meeting/draftList", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | export function saveDraft(data) { |
| | | return request({ |
| | | url: "/meeting/saveDraft", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | export function delDraft(id) { |
| | | return request({ |
| | | url: "/meeting/delDraft/"+id, |
| | | method: "delete", |
| | | }); |
| | | } |
| | | |
| | | export function saveMeetingApplication(data){ |
| | | return request({ |
| | | url: "/meeting/saveMeetingApplication", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | export function getExamineList(data) { |
| | | return request({ |
| | | url: "/meeting/applicationList", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | |
| | | export function getMeetingUseList(data){ |
| | | return request({ |
| | | url: "/meeting/meetingUseList", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | export function getMeetingPublish(data){ |
| | | return request({ |
| | | url: "/meeting/meetingPublishList", |
| | | method: "post", |
| | | data: data |
| | | }); |
| | | } |
| | | |
| | | |
| | | export function getMeetingMinutesByMeetingId(id){ |
| | | return request({ |
| | | url: "/meeting/getMeetingMinutesByMeetingId/"+id, |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | export function saveMeetingMinutes(data){ |
| | | return request({ |
| | | url: "/meeting/saveMeetingMinutes", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | |
| | | export function getMeetSummary(){ |
| | | return request({ |
| | | url: "/meeting/getMeetSummary", |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | export function getMeetSummaryItems(){ |
| | | return request({ |
| | | url: "/meeting/getMeetSummaryItems", |
| | | method: "get", |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | // æ¥è¯¢åºåé¢è¦å°è´¦å表 |
| | | export const getStockWarningLedgerPage = (params) => { |
| | | return request({ |
| | | url: "/stockWarningLedger/listPage", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // é宿¥ä»·é¡µé¢æ¥å£ |
| | | import request from "@/utils/request"; |
| | | |
| | | // å页æ¥è¯¢æ¥ä»·åå表 |
| | | export function getQuotationList(query) { |
| | | return request({ |
| | | url: "/sales/quotation/list", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // æ¥è¯¢æ¥ä»·å详æ
|
| | | export function getQuotationDetail(query) { |
| | | return request({ |
| | | url: "/sales/quotation/detail", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // æ°å¢æ¥ä»·å |
| | | export function addQuotation(data) { |
| | | return request({ |
| | | url: "/sales/quotation/add", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // ä¿®æ¹æ¥ä»·å |
| | | export function updateQuotation(data) { |
| | | return request({ |
| | | url: "/sales/quotation/update", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // å 餿¥ä»·å |
| | | export function deleteQuotation(query) { |
| | | return request({ |
| | | url: "/sales/quotation/delete", |
| | | method: "delete", |
| | | data: query, |
| | | }); |
| | | } |
| | | |
| | | // å鿥价å |
| | | export function sendQuotation(data) { |
| | | return request({ |
| | | url: "/sales/quotation/send", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // æ¥ä»·å转订å |
| | | export function convertToOrder(data) { |
| | | return request({ |
| | | url: "/sales/quotation/convertToOrder", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // æ¥è¯¢å®¢æ·å表 |
| | | export function getCustomerList(query) { |
| | | return request({ |
| | | url: "/basic/customer/list", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // æ¥è¯¢äº§åå表 |
| | | export function getProductList(query) { |
| | | return request({ |
| | | url: "/basic/product/list", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // æ¥è¯¢ä¸å¡åå表 |
| | | export function getSalespersonList(query) { |
| | | return request({ |
| | | url: "/system/user/salespersonList", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // å¯¼åºæ¥ä»·å |
| | | export function exportQuotation(query) { |
| | | return request({ |
| | | url: "/sales/quotation/export", |
| | | method: "get", |
| | | params: query, |
| | | responseType: "blob", |
| | | }); |
| | | } |
| | | |
| | | // æå°æ¥ä»·å |
| | | export function printQuotation(query) { |
| | | return request({ |
| | | url: "/sales/quotation/print", |
| | | method: "get", |
| | | params: query, |
| | | responseType: "blob", |
| | | }); |
| | | } |
| | |
| | | width="700px" |
| | | @close="closeDia" |
| | | > |
| | | <el-form :model="form" label-width="140px" label-position="top" ref="formRef"> |
| | | <el-form :model="form" :rules="rules" label-width="140px" label-position="top" ref="formRef"> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="æµç¨ç¼å·ï¼" prop="approveId"> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-row v-if="!isQuotationApproval"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="审æ¹äºç±ï¼" prop="approveReason"> |
| | | <el-input v-model="form.approveReason" placeholder="请è¾å
¥" clearable type="textarea" disabled/> |
| | |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | |
| | | <!-- æ¥ä»·å®¡æ¹ï¼å±ç¤ºæ¥ä»·è¯¦æ
ï¼å¤ç¨é宿¥ä»·âæ¥ç详æ
å¯¹è¯æ¡âå
å®¹ç»æï¼ --> |
| | | <div v-if="isQuotationApproval" style="margin: 10px 0 18px;"> |
| | | <el-divider content-position="left">æ¥ä»·è¯¦æ
</el-divider> |
| | | <el-skeleton :loading="quotationLoading" animated> |
| | | <template #template> |
| | | <el-skeleton-item variant="h3" style="width: 30%" /> |
| | | <el-skeleton-item variant="text" style="width: 100%" /> |
| | | <el-skeleton-item variant="text" style="width: 100%" /> |
| | | </template> |
| | | <template #default> |
| | | <el-empty v-if="!currentQuotation || !currentQuotation.quotationNo" description="æªæ¥è¯¢å°å¯¹åºæ¥ä»·è¯¦æ
" /> |
| | | <template v-else> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions-item label="æ¥ä»·åå·">{{ currentQuotation.quotationNo }}</el-descriptions-item> |
| | | <el-descriptions-item label="客æ·åç§°">{{ currentQuotation.customer }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä¸å¡å">{{ currentQuotation.salesperson }}</el-descriptions-item> |
| | | <el-descriptions-item label="æ¥ä»·æ¥æ">{{ currentQuotation.quotationDate }}</el-descriptions-item> |
| | | <el-descriptions-item label="æææè³">{{ currentQuotation.validDate }}</el-descriptions-item> |
| | | <el-descriptions-item label="仿¬¾æ¹å¼">{{ currentQuotation.paymentMethod }}</el-descriptions-item> |
| | | <el-descriptions-item label="æ¥ä»·æ»é¢" :span="2"> |
| | | <span style="font-size: 18px; color: #e6a23c; font-weight: bold;"> |
| | | ¥{{ Number(currentQuotation.totalAmount ?? 0).toFixed(2) }} |
| | | </span> |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div style="margin-top: 20px;"> |
| | | <h4>产åæç»</h4> |
| | | <el-table :data="currentQuotation.products || []" border style="width: 100%"> |
| | | <el-table-column prop="product" label="产ååç§°" /> |
| | | <el-table-column prop="specification" label="è§æ ¼åå·" /> |
| | | <el-table-column prop="unit" label="åä½" /> |
| | | <el-table-column prop="unitPrice" label="åä»·"> |
| | | <template #default="scope">Â¥{{ Number(scope.row.unitPrice ?? 0).toFixed(2) }}</template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <div v-if="currentQuotation.remark" style="margin-top: 20px;"> |
| | | <h4>夿³¨</h4> |
| | | <p>{{ currentQuotation.remark }}</p> |
| | | </div> |
| | | </template> |
| | | </template> |
| | | </el-skeleton> |
| | | </div> |
| | | |
| | | <el-form :model="{ activities }" ref="formRef" label-position="top"> |
| | | <el-steps :active="getActiveStep()" finish-status="success" process-status="process" align-center direction="vertical"> |
| | | <el-step |
| | |
| | | <template #footer v-if="operationType === 'approval'"> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitForm(2)">ä¸éè¿</el-button> |
| | | <el-button type="primary" @click="openSignatureDialog(1)">éè¿</el-button> |
| | | <el-button type="primary" @click="submitForm(1)">éè¿</el-button> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <!-- çµåç¾åå¼¹çªï¼vue3-signature-padï¼ --> |
| | | <el-dialog v-model="signatureDialogVisible" title="çµåç¾å" width="600px" append-to-body> |
| | | <vueEsign |
| | | ref="esign" |
| | | class="mySign" |
| | | :width="800" |
| | | :height="300" |
| | | :isCrop="isCrop" |
| | | :lineWidth="lineWidth" |
| | | :lineColor="lineColor" |
| | | /> |
| | | <div style="margin-top:10px;"> |
| | | <el-button @click="clearSignature">æ¸
é¤</el-button> |
| | | <el-button type="primary" @click="confirmSignature">ç¡®å®</el-button> |
| | | </div> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { getCurrentInstance, reactive, ref, toRefs } from "vue"; |
| | | import vueEsign from "vue-esign"; |
| | | import { computed, getCurrentInstance, reactive, ref, toRefs } from "vue"; |
| | | import { |
| | | approveProcessDetails, |
| | | getDept, |
| | |
| | | import useUserStore from "@/store/modules/user.js"; |
| | | import {userListNoPageByTenantId} from "@/api/system/user.js"; |
| | | import { WarningFilled, Edit, Check, MoreFilled } from '@element-plus/icons-vue' |
| | | import { getToken } from "@/utils/auth"; |
| | | import { getQuotationList } from "@/api/salesManagement/salesQuotation.js"; |
| | | const emit = defineEmits(['close']) |
| | | const { proxy } = getCurrentInstance() |
| | | |
| | | const props = defineProps({ |
| | | approveType: { |
| | | type: [Number, String], |
| | | default: 0 |
| | | } |
| | | }) |
| | | |
| | | const dialogFormVisible = ref(false); |
| | | const operationType = ref('') |
| | |
| | | const userStore = useUserStore() |
| | | const productOptions = ref([]); |
| | | const userList = ref([]) |
| | | const quotationLoading = ref(false) |
| | | const currentQuotation = ref({}) |
| | | const isQuotationApproval = computed(() => Number(props.approveType) === 6) |
| | | |
| | | const data = reactive({ |
| | | form: { |
| | | approveTime: "", |
| | |
| | | approveReason: "", |
| | | checkResult: "", |
| | | }, |
| | | rules: { |
| | | // 使ç¨é¨é¨IDåå¿
å¡«æ ¡éªï¼é¿å
åç§°æªåæ¥å¯¼è´è¯¯æ¥ |
| | | approveDeptId: [{ required: true, message: "è¯·éæ©ç³è¯·é¨é¨", trigger: "change" }], |
| | | }, |
| | | }); |
| | | const { form } = toRefs(data); |
| | | const signatureDialogVisible = ref(false); |
| | | const signatureImg = ref(''); |
| | | let submitStatus = null; // 临æ¶åå¨éè¿/ä¸éè¿ç¶æ |
| | | const isCrop = ref(""); |
| | | const esign = ref(null); |
| | | const lineWidth = ref(0); |
| | | const lineColor = ref("#000000"); |
| | | |
| | | // ä¸ä¼ é
ç½® |
| | | const upload = reactive({ |
| | | // ä¸ä¼ çå°å |
| | | url: import.meta.env.VITE_APP_BASE_API + "/file/upload", |
| | | // 设置ä¸ä¼ ç请æ±å¤´é¨ |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | }); |
| | | const { form, rules } = toRefs(data); |
| | | |
| | | // èç¹æ é¢ |
| | | const getNodeTitle = (index, len) => { |
| | |
| | | const openDialog = (type, row) => { |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | currentQuotation.value = {} |
| | | userListNoPageByTenantId().then((res) => { |
| | | userList.value = res.data; |
| | | }); |
| | | form.value = {...row} |
| | | getProductOptions() |
| | | |
| | | // æ¥ä»·å®¡æ¹ï¼ç¨å®¡æ¹äºç±å段æ¿è½½çâæ¥ä»·åå·â廿¥æ¥ä»·å表 |
| | | if (isQuotationApproval.value) { |
| | | const quotationNo = row?.approveReason; |
| | | if (quotationNo) { |
| | | quotationLoading.value = true |
| | | getQuotationList({ quotationNo }).then((res) => { |
| | | const records = res?.data?.records || [] |
| | | currentQuotation.value = records[0] || {} |
| | | }).finally(() => { |
| | | quotationLoading.value = false |
| | | }) |
| | | } |
| | | } |
| | | |
| | | approveProcessDetails(row.approveId).then((res) => { |
| | | activities.value = res.data |
| | | // å¢å isApprovalåæ®µ |
| | |
| | | productOptions.value = res.data; |
| | | }); |
| | | }; |
| | | // æå¼ç¾åå¼¹çª |
| | | const openSignatureDialog = (status) => { |
| | | submitStatus = status; |
| | | signatureDialogVisible.value = true; |
| | | }; |
| | | // æ¸
é¤ç¾å |
| | | const clearSignature = () => { |
| | | esign.value.reset(); |
| | | }; |
| | | // 确认ç¾å |
| | | const confirmSignature = () => { |
| | | esign.value.generate().then((res) => { |
| | | console.log(res); |
| | | // å°base64转æ¢ä¸ºäºè¿å¶ |
| | | const base64Data = res.split(',')[1]; // ç§»é¤data:image/png;base64,åç¼ |
| | | const binaryString = atob(base64Data); |
| | | const bytes = new Uint8Array(binaryString.length); |
| | | for (let i = 0; i < binaryString.length; i++) { |
| | | bytes[i] = binaryString.charCodeAt(i); |
| | | } |
| | | signatureImg.value = bytes; |
| | | |
| | | // å建æä»¶å¯¹è±¡ç¨äºä¸ä¼ |
| | | const blob = new Blob([bytes], { type: 'image/png' }); |
| | | const file = new File([blob], 'signature.png', { type: 'image/png' }); |
| | | |
| | | // å建FormData |
| | | const formData = new FormData(); |
| | | formData.append('file', file); |
| | | |
| | | // ä¸ä¼ ç¾åå¾ç |
| | | fetch(upload.url, { |
| | | method: 'POST', |
| | | headers: upload.headers, |
| | | body: formData |
| | | }) |
| | | .then(response => response.json()) |
| | | .then(data => { |
| | | if (data.code === 200) { |
| | | console.log('data---', data) |
| | | let tempFileIds = []; |
| | | tempFileIds.push(data.data.tempId); |
| | | signatureDialogVisible.value = false; |
| | | clearSignature(); |
| | | // åªæéè¿æ¶æä¼ éç¾åæä»¶ID |
| | | if (submitStatus === 1) { |
| | | submitForm(submitStatus, tempFileIds); |
| | | } else { |
| | | submitForm(submitStatus); |
| | | } |
| | | } else { |
| | | proxy.$modal.msgError("ç¾åå¾çä¸ä¼ 失败ï¼" + data.msg); |
| | | } |
| | | }) |
| | | .catch(error => { |
| | | console.error('ä¸ä¼ 失败:', error); |
| | | proxy.$modal.msgError("ç¾åå¾çä¸ä¼ 失败"); |
| | | }); |
| | | }).catch((err) => { |
| | | console.log(err); |
| | | proxy.$modal.msgWarning("请å
ç¾åï¼"); |
| | | }) |
| | | }; |
| | | // æäº¤å®¡æ¹ |
| | | const submitForm = (status, tempFileIds) => { |
| | | const submitForm = (status) => { |
| | | const filteredActivities = activities.value.filter(activity => activity.isShen); |
| | | filteredActivities[0].approveNodeStatus = status; |
| | | // åªæéè¿æ¶æéè¦ç¾å |
| | | if (status === 1 && tempFileIds) { |
| | | filteredActivities[0].tempFileIds = tempFileIds; |
| | | } |
| | | // 夿æ¯å¦ä¸ºæå䏿¥ |
| | | const isLast = activities.value.findIndex(a => a.isShen) === activities.value.length-1; |
| | | updateApproveNode({ ...filteredActivities[0], isLast }).then(() => { |
| | |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | | dialogFormVisible.value = false; |
| | | quotationLoading.value = false |
| | | currentQuotation.value = {} |
| | | emit('close') |
| | | }; |
| | | defineExpose({ |
| | |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <!-- ç³è¯·é¨é¨ï¼æ ¡éªä½¿ç¨é¨é¨IDï¼ä¾¿äºä¸æéæ©åç«å³éè¿æ ¡éª --> |
| | | <el-form-item label="ç³è¯·é¨é¨ï¼" prop="approveDeptId"> |
| | | <!-- <el-input v-model="form.approveDeptName" placeholder="请è¾å
¥" clearable/>--> |
| | | <el-select |
| | | disabled |
| | | v-model="form.approveDeptId" |
| | | placeholder="éæ©é¨é¨" |
| | | @change="handleDeptChange" |
| | | > |
| | | <el-option |
| | | v-for="user in productOptions" |
| | |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="审æ¹äºç±ï¼" prop="approveReason"> |
| | | <el-form-item :label="props.approveType == 5 ? 'éè´è¯´æï¼' : '审æ¹äºç±ï¼'" prop="approveReason"> |
| | | <el-input v-model="form.approveReason" placeholder="请è¾å
¥" clearable type="textarea" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <!-- è¯·åæ¶é´ï¼ä»
å½ approveType 为 2 æ¶æ¾ç¤ºï¼ --> |
| | | <el-row :gutter="30" v-if="props.approveType == 2"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="请åå¼å§æ¶é´ï¼" prop="startDate"> |
| | | <el-date-picker |
| | | v-model="form.startDate" |
| | | type="date" |
| | | placeholder="è¯·éæ©å¼å§æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | clearable |
| | | 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" |
| | | placeholder="è¯·éæ©ç»ææ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | clearable |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <!-- æ¥ééé¢ï¼ä»
å½ approveType 为 4 æ¶æ¾ç¤ºï¼ --> |
| | | <el-row v-if="props.approveType == 4"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="æ¥ééé¢ï¼" prop="price"> |
| | | <el-input-number |
| | | v-model="form.price" |
| | | placeholder="请è¾å
¥æ¥ééé¢" |
| | | :min="0" |
| | | :precision="2" |
| | | :step="0.01" |
| | | style="width: 100%" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <!-- åºå·®å°ç¹ï¼ä»
å½ approveType 为 3 æ¶æ¾ç¤ºï¼ --> |
| | | <el-row v-if="props.approveType == 3"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="夿³¨ï¼" prop="location"> |
| | | <el-input |
| | | v-model="form.location" |
| | | placeholder="请è¾å
¥å¤æ³¨" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | <el-select |
| | | v-model="form.approveUser" |
| | | placeholder="éæ©äººå" |
| | | filterable |
| | | default-first-option |
| | | :reserve-keyword="false" |
| | | > |
| | | <el-option |
| | | v-for="user in userList" |
| | |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits(['close']) |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { getCurrentDate } from "@/utils/index.js"; |
| | | import log from "@/views/monitor/job/log.vue"; |
| | | const userStore = useUserStore(); |
| | | |
| | | const dialogFormVisible = ref(false); |
| | |
| | | approveTime: "", |
| | | approveId: "", |
| | | approveUser: "", |
| | | approveDeptId: "", |
| | | approveDeptId: "", |
| | | approveDeptName: "", |
| | | approveReason: "", |
| | | checkResult: "", |
| | | tempFileIds: [], |
| | | approverList: [] // æ°å¢å段ï¼å卿æèç¹ç审æ¹äººid |
| | | approverList: [], // æ°å¢å段ï¼å卿æèç¹ç审æ¹äººid |
| | | startDate: "", // 请åå¼å§æ¶é´ |
| | | endDate: "", // 请åç»ææ¶é´ |
| | | price: null, // æ¥ééé¢ |
| | | location: "" // åºå·®å°ç¹ |
| | | }, |
| | | rules: { |
| | | approveTime: [{ required: false, message: "请è¾å
¥", trigger: "change" },], |
| | | approveTime: [{ required: false, message: "请è¾å
¥", trigger: "change" }], |
| | | approveId: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | | approveUser: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | | approveDeptId: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | // 使ç¨é¨é¨IDåå¿
å¡«æ ¡éªï¼é¿å
åç§°æªåæ¥å¯¼è´è¯¯æ¥ |
| | | approveDeptId: [{ required: true, message: "è¯·éæ©ç³è¯·é¨é¨", trigger: "change" }], |
| | | approveReason: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | checkResult: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | | startDate: [{ required: true, message: "è¯·éæ©è¯·åå¼å§æ¶é´", trigger: "change" }], |
| | | endDate: [{ required: true, message: "è¯·éæ©è¯·åç»ææ¶é´", trigger: "change" }], |
| | | price: [{ required: true, message: "请è¾å
¥æ¥ééé¢", trigger: "blur" }], |
| | | location: [{ required: true, message: "请è¾å
¥åºå·®å°ç¹", trigger: "blur" }], |
| | | }, |
| | | }); |
| | | const { form, rules } = toRefs(data); |
| | |
| | | function removeApproverNode(index) { |
| | | approverNodes.value.splice(index, 1) |
| | | } |
| | | |
| | | // å¤çé¨é¨éæ©åå |
| | | const handleDeptChange = (deptId) => { |
| | | if (deptId) { |
| | | const selectedDept = productOptions.value.find(dept => dept.deptId === deptId); |
| | | if (selectedDept) { |
| | | form.value.approveDeptName = selectedDept.deptName; |
| | | } |
| | | } else { |
| | | form.value.approveDeptName = ''; |
| | | } |
| | | }; |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = (type, row) => { |
| | | console.log('openDialog', type, row) |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | userListNoPageByTenantId().then((res) => { |
| | |
| | | proxy.$modal.msgError("请为ææå®¡æ¹èç¹éæ©å®¡æ¹äººï¼") |
| | | return |
| | | } |
| | | // å½ approveType 为 2 æ¶ï¼æ ¡éªè¯·åæ¶é´ |
| | | if (props.approveType == 2) { |
| | | if (!form.value.startDate) { |
| | | proxy.$modal.msgError("è¯·éæ©è¯·åå¼å§æ¶é´ï¼") |
| | | return |
| | | } |
| | | if (!form.value.endDate) { |
| | | proxy.$modal.msgError("è¯·éæ©è¯·åç»ææ¶é´ï¼") |
| | | return |
| | | } |
| | | // æ ¡éªç»ææ¶é´ä¸è½æ©äºå¼å§æ¶é´ |
| | | if (new Date(form.value.endDate) < new Date(form.value.startDate)) { |
| | | proxy.$modal.msgError("请åç»ææ¶é´ä¸è½æ©äºå¼å§æ¶é´ï¼") |
| | | return |
| | | } |
| | | } |
| | | // å½ approveType 为 3 æ¶ï¼æ ¡éªåºå·®å°ç¹ |
| | | if (props.approveType == 3) { |
| | | if (!form.value.location || form.value.location.trim() === '') { |
| | | proxy.$modal.msgError("请è¾å
¥åºå·®å°ç¹ï¼") |
| | | return |
| | | } |
| | | } |
| | | // å½ approveType 为 4 æ¶ï¼æ ¡éªæ¥ééé¢ |
| | | if (props.approveType == 4) { |
| | | if (!form.value.price || form.value.price <= 0) { |
| | | proxy.$modal.msgError("请è¾å
¥ææçæ¥ééé¢ï¼") |
| | | return |
| | | } |
| | | } |
| | | proxy.$refs.formRef.validate(valid => { |
| | | if (valid) { |
| | | if (operationType.value === "add" || currentApproveStatus.value == 3) { |
| | |
| | | dialogFormVisible.value = false; |
| | | emit('close') |
| | | }; |
| | | // è·åå½åæ¥æå¹¶æ ¼å¼å为 YYYY-MM-DD |
| | | function getCurrentDate() { |
| | | const today = new Date(); |
| | | const year = today.getFullYear(); |
| | | const month = String(today.getMonth() + 1).padStart(2, "0"); // æä»½ä»0å¼å§ |
| | | const day = String(today.getDate()).padStart(2, "0"); |
| | | return `${year}-${month}-${day}`; |
| | | } |
| | | |
| | | // ä¸ä¼ åæ ¡æ£ |
| | | function handleBeforeUpload(file) { |
| | |
| | | <template> |
| | | <el-dialog v-model="dialogVisible" title="éä»¶" width="40%" :before-close="handleClose"> |
| | | <el-table :data="tableData" border height="40vh" stripe> |
| | | <el-dialog v-model="dialogVisible" title="éä»¶" width="40%" :before-close="handleClose" draggable> |
| | | <el-table :data="tableData" border height="40vh"> |
| | | <el-table-column label="éä»¶åç§°" prop="name" min-width="400" show-overflow-tooltip /> |
| | | <el-table-column fixed="right" label="æä½" width="100" align="center"> |
| | | <el-table-column fixed="right" label="æä½" width="150" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" size="small" @click="downLoadFile(scope.row)">ä¸è½½</el-button> |
| | | <el-button link type="primary" size="small" @click="lookFile(scope.row)">é¢è§</el-button> |
| | | <el-button link type="danger" size="small" @click="handleDelete(scope.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | |
| | | <script setup> |
| | | import { ref } from 'vue' |
| | | import filePreview from '@/components/filePreview/index.vue' |
| | | import { ElMessageBox, ElMessage } from 'element-plus' |
| | | import { delCommonFile } from '@/api/publicApi/commonFile.js' |
| | | |
| | | const dialogVisible = ref(false) |
| | | const tableData = ref([]) |
| | |
| | | const lookFile = (row) => { |
| | | filePreviewRef.value.open(row.url) |
| | | } |
| | | // å é¤éä»¶ |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm(`确认å é¤éä»¶"${row.name}"åï¼`, 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | delCommonFile([row.id]).then(() => { |
| | | ElMessage.success('å 餿å') |
| | | // ä»å表ä¸ç§»é¤å·²å é¤çéä»¶ |
| | | const index = tableData.value.findIndex(item => item.id === row.id) |
| | | if (index !== -1) { |
| | | tableData.value.splice(index, 1) |
| | | } |
| | | }).catch(() => { |
| | | ElMessage.error('å é¤å¤±è´¥') |
| | | }) |
| | | }).catch(() => { |
| | | ElMessage.info('已忶å é¤') |
| | | }) |
| | | } |
| | | defineExpose({ |
| | | open |
| | | }) |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- æ ç¾é¡µåæ¢ä¸åç审æ¹ç±»å --> |
| | | <el-tabs v-model="activeTab" @tab-change="handleTabChange" class="approval-tabs"> |
| | | <el-tab-pane label="åå审æ¹" name="1"></el-tab-pane> |
| | | <el-tab-pane label="请å管ç" name="2"></el-tab-pane> |
| | | <el-tab-pane label="éå®å®¡æ¹" name="3"></el-tab-pane> |
| | | <el-tab-pane label="æ¥é审æ¹" name="4"></el-tab-pane> |
| | | <el-tab-pane label="éè´å®¡æ¹" name="5"></el-tab-pane> |
| | | <el-tab-pane label="æ¥ä»·å®¡æ¹" name="6"></el-tab-pane> |
| | | <el-tab-pane label="åºåºå®¡æ¹" name="7"></el-tab-pane> |
| | | </el-tabs> |
| | | |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">æµç¨ç¼å·ï¼</span> |
| | |
| | | > |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" @click="openForm('add')">æ°å¢</el-button> |
| | | <!-- <el-button @click="handleOut">导åº</el-button>--> |
| | | <el-button type="primary" @click="openForm('add')" v-if="currentApproveType !== 6">æ°å¢</el-button> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :column="tableColumnCopy" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | |
| | | :total="page.total" |
| | | ></PIMTable> |
| | | </div> |
| | | <info-form-dia ref="infoFormDia" @close="handleQuery" :approveType="approveType"></info-form-dia> |
| | | <approval-dia ref="approvalDia" @close="handleQuery"></approval-dia> |
| | | <info-form-dia ref="infoFormDia" @close="handleQuery" :approveType="currentApproveType"></info-form-dia> |
| | | <approval-dia ref="approvalDia" @close="handleQuery" :approveType="currentApproveType"></approval-dia> |
| | | <FileList ref="fileListRef" /> |
| | | </div> |
| | | </template> |
| | |
| | | <script setup> |
| | | import FileList from "./fileList.vue"; |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import {onMounted, ref} from "vue"; |
| | | import {onMounted, ref, computed, reactive, toRefs, nextTick, getCurrentInstance} from "vue"; |
| | | import {ElMessageBox} from "element-plus"; |
| | | import { useRoute } from 'vue-router'; |
| | | import InfoFormDia from "@/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue"; |
| | | import ApprovalDia from "@/views/collaborativeApproval/approvalProcess/components/approvalDia.vue"; |
| | | import {approveProcessDelete, approveProcessListPage} from "@/api/collaborativeApproval/approvalProcess.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | |
| | | // å®ä¹ç»ä»¶æ¥æ¶çprops |
| | | const props = defineProps({ |
| | | approveType: { |
| | | type: [Number, String], |
| | | default: 0 |
| | | } |
| | | const userStore = useUserStore(); |
| | | const route = useRoute(); |
| | | |
| | | // å½åéä¸çæ ç¾é¡µï¼é»è®¤ä¸ºå
¬åºç®¡ç |
| | | const activeTab = ref('1'); |
| | | |
| | | // å½å审æ¹ç±»åï¼æ ¹æ®éä¸çæ ç¾é¡µè®¡ç® |
| | | const currentApproveType = computed(() => { |
| | | return Number(activeTab.value); |
| | | }); |
| | | |
| | | const userStore = useUserStore(); |
| | | // æ ç¾é¡µåæ¢å¤ç |
| | | const handleTabChange = (tabName) => { |
| | | // 忢æ ç¾é¡µæ¶éç½®æç´¢æ¡ä»¶åå页ï¼å¹¶éæ°å è½½æ°æ® |
| | | searchForm.value.approveId = ''; |
| | | searchForm.value.approveStatus = ''; |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | |
| | | const data = reactive({ |
| | |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "审æ¹ç¶æ", |
| | | prop: "approveStatus", |
| | | dataType: "tag", |
| | | width: 100, |
| | | formatData: (params) => { |
| | | if (params == 0) { |
| | | return "å¾
å®¡æ ¸"; |
| | | } else if (params == 1) { |
| | | return "å®¡æ ¸ä¸"; |
| | | } else if (params == 2) { |
| | | return "å®¡æ ¸å®æ"; |
| | | } else if (params == 4) { |
| | | return "已鿰æäº¤"; |
| | | } else { |
| | | return 'ä¸éè¿'; |
| | | } |
| | | |
| | | // å¨æè¡¨æ ¼åé
ç½®ï¼æ ¹æ®å®¡æ¹ç±»åçæå |
| | | const tableColumnCopy = computed(() => { |
| | | const isLeaveType = currentApproveType.value === 2; // 请å管ç |
| | | const isReimburseType = currentApproveType.value === 4; // æ¥é管ç |
| | | const isQuotationType = currentApproveType.value === 6; // æ¥ä»·å®¡æ¹ |
| | | |
| | | // åºç¡åé
ç½® |
| | | const baseColumns = [ |
| | | { |
| | | label: "审æ¹ç¶æ", |
| | | prop: "approveStatus", |
| | | dataType: "tag", |
| | | width: 100, |
| | | formatData: (params) => { |
| | | if (params == 0) { |
| | | return "å¾
å®¡æ ¸"; |
| | | } else if (params == 1) { |
| | | return "å®¡æ ¸ä¸"; |
| | | } else if (params == 2) { |
| | | return "å®¡æ ¸å®æ"; |
| | | } else if (params == 4) { |
| | | return "已鿰æäº¤"; |
| | | } else { |
| | | return 'ä¸éè¿'; |
| | | } |
| | | }, |
| | | formatType: (params) => { |
| | | if (params == 0) { |
| | | return "warning"; |
| | | } else if (params == 1) { |
| | | return "primary"; |
| | | } else if (params == 2) { |
| | | return "success"; |
| | | } else if (params == 4) { |
| | | return "info"; |
| | | } else { |
| | | return 'danger'; |
| | | } |
| | | }, |
| | | }, |
| | | formatType: (params) => { |
| | | if (params == 0) { |
| | | return "warning"; |
| | | } else if (params == 1) { |
| | | return "primary"; |
| | | } else if (params == 2) { |
| | | return "success"; |
| | | } else if (params == 4) { |
| | | return ""; |
| | | } else { |
| | | return 'danger'; |
| | | } |
| | | { |
| | | label: "æµç¨ç¼å·", |
| | | prop: "approveId", |
| | | width: 170 |
| | | }, |
| | | }, |
| | | { |
| | | label: "æµç¨ç¼å·", |
| | | prop: "approveId", |
| | | width: 170 |
| | | }, |
| | | { |
| | | label: "ç³è¯·é¨é¨", |
| | | prop: "approveDeptName", |
| | | width: 220 |
| | | }, |
| | | { |
| | | label: "审æ¹äºç±", |
| | | prop: "approveReason", |
| | | width: 200 |
| | | }, |
| | | { |
| | | label: "ç³è¯·äºº", |
| | | prop: "approveUserName", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "ç³è¯·æ¥æ", |
| | | prop: "approveTime", |
| | | width: 200 |
| | | }, |
| | | { |
| | | label: "ç»ææ¥æ", |
| | | prop: "approveOverTime", |
| | | width: 120 |
| | | }, |
| | | { |
| | | { |
| | | label: "ç³è¯·é¨é¨", |
| | | prop: "approveDeptName", |
| | | width: 220 |
| | | }, |
| | | { |
| | | label: isQuotationType ? "æ¥ä»·åå·" : "审æ¹äºç±", |
| | | prop: "approveReason", |
| | | width: 200 |
| | | }, |
| | | { |
| | | label: "ç³è¯·äºº", |
| | | prop: "approveUserName", |
| | | width: 120 |
| | | } |
| | | ]; |
| | | |
| | | // éé¢åï¼ä»
æ¥éç®¡çæ¾ç¤ºï¼ |
| | | if (isReimburseType) { |
| | | baseColumns.push({ |
| | | label: "éé¢ï¼å
ï¼", |
| | | prop: "price", |
| | | width: 120 |
| | | }); |
| | | } |
| | | |
| | | // æ¥æåï¼æ ¹æ®ç±»å卿é
ç½®ï¼ |
| | | baseColumns.push( |
| | | { |
| | | label: isLeaveType ? "å¼å§æ¥æ" : "ç³è¯·æ¥æ", |
| | | prop: isLeaveType ? "startDate" : "approveTime", |
| | | width: 200 |
| | | }, |
| | | { |
| | | label: "ç»ææ¥æ", |
| | | prop: isLeaveType ? "endDate" : "approveOverTime", |
| | | width: 120 |
| | | } |
| | | ); |
| | | |
| | | // å½å审æ¹äººå |
| | | baseColumns.push({ |
| | | label: "å½å审æ¹äºº", |
| | | prop: "approveUserCurrentName", |
| | | width: 120 |
| | | }, |
| | | { |
| | | }); |
| | | |
| | | // æä½å |
| | | baseColumns.push({ |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | }, |
| | | disabled: (row) => row.approveStatus == 2 || row.approveStatus == 1 || row.approveStatus == 4 |
| | | disabled: (row) => currentApproveType.value === 6 || row.approveStatus == 2 || row.approveStatus == 1 || row.approveStatus == 4 |
| | | }, |
| | | { |
| | | name: "å®¡æ ¸", |
| | |
| | | clickFun: (row) => { |
| | | openApprovalDia("approval", row); |
| | | }, |
| | | disabled: (row) => row.approveUserCurrentId == null || row.approveStatus == 2 || row.approveStatus == 3 || row.approveStatus == 4 || row.approveUserCurrentId !== userStore.id |
| | | disabled: (row) => row.approveUserCurrentId == null || row.approveStatus == 2 || row.approveStatus == 3 || row.approveStatus == 4 || row.approveUserCurrentId !== userStore.id |
| | | }, |
| | | { |
| | | name: "详æ
", |
| | |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | }); |
| | | |
| | | return baseColumns; |
| | | }); |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | approveProcessListPage({...page, ...searchForm.value,approveType:props.approveType}).then(res => { |
| | | approveProcessListPage({...page, ...searchForm.value, approveType: currentApproveType.value}).then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records |
| | | page.total = res.data.total; |
| | |
| | | tableLoading.value = false; |
| | | }) |
| | | }; |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | const type = currentApproveType.value |
| | | const urlMap = { |
| | | 0: "/approveProcess/exportZero", |
| | | 1: "/approveProcess/exportOne", |
| | | 2: "/approveProcess/exportTwo", |
| | | 3: "/approveProcess/exportThree", |
| | | 4: "/approveProcess/exportFour", |
| | | 5: "/approveProcess/exportFive", |
| | | 6: "/approveProcess/exportSix", |
| | | 7: "/approveProcess/exportSeven", |
| | | } |
| | | const url = urlMap[type] || urlMap[0] |
| | | const nameMap = { |
| | | 0: "åå审æ¹ç®¡ç表", |
| | | 1: "å
¬åºç®¡ç审æ¹è¡¨", |
| | | 2: "请å管ç审æ¹è¡¨", |
| | | 3: "åºå·®ç®¡ç审æ¹è¡¨", |
| | | 4: "æ¥é管ç审æ¹è¡¨", |
| | | 5: "éè´ç³è¯·å®¡æ¹è¡¨", |
| | | 6: "æ¥ä»·å®¡æ¹è¡¨", |
| | | 7: "åºåºå®¡æ¹è¡¨", |
| | | } |
| | | const fileName = nameMap[type] || nameMap[0] |
| | | proxy.download(url, {}, `${fileName}.xlsx`) |
| | | } |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | |
| | | }); |
| | | }; |
| | | onMounted(() => { |
| | | // æ ¹æ®URLåæ°è®¾ç½®æ ç¾é¡µåæ¥è¯¢æ¡ä»¶ |
| | | const approveType = route.query.approveType; |
| | | const approveId = route.query.approveId; |
| | | |
| | | if (approveType) { |
| | | // 设置æ ç¾é¡µï¼approveType å¯¹åº activeTab ç nameï¼ |
| | | activeTab.value = String(approveType); |
| | | } |
| | | |
| | | if (approveId) { |
| | | // 设置æµç¨ç¼å·æ¥è¯¢æ¡ä»¶ |
| | | searchForm.value.approveId = String(approveId); |
| | | } |
| | | |
| | | // æ¥è¯¢å表 |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped></style> |
| | | <style scoped> |
| | | .approval-tabs { |
| | | margin-bottom: 10px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="container"> |
| | | <!-- å¼å
¥index.vueç»ä»¶å¹¶ä¼ éåæ° --> |
| | | <ApprovalProcessIndex :approveType="5" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import ApprovalProcessIndex from './index.vue' |
| | | |
| | | // å®ä¹ç»ä»¶åç§° |
| | | defineOptions({ |
| | | name: 'ApprovalProcessIndex1' |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .container { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | </style> |
| | |
| | | <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;" stripe> |
| | | |
| | | <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-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;" stripe> |
| | | <el-table-column prop="employeeType" label="å工类å"/> |
| | | |
| | | <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-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;" stripe> |
| | | |
| | | <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-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;" stripe> |
| | | |
| | | <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 ? 'success' : 'info'"> |
| | | {{ scope.row.flexibleStart ? 'æ¯' : 'å¦' }} |
| | | <el-tag :type="scope.row.flexibleStart === 'true' ? 'success' : 'info'"> |
| | | {{ scope.row.flexibleStart === 'true' ? 'æ¯' : 'å¦' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | </el-table> |
| | | </div> |
| | | </el-tab-pane> |
| | | |
| | | <!-- æå¡è®°å½ --> |
| | | <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-select> |
| | | <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"> |
| | | <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> |
| | | </div> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | |
| | | <!-- éç¨å¼¹çª --> |
| | |
| | | <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-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-option label="æ£å¼åå·¥" value="regular" /> |
| | | <!-- <el-option label="æ£å¼åå·¥" value="regular" /> |
| | | <el-option label="è¯ç¨æåå·¥" value="probation" /> |
| | | <el-option label="å®ä¹ ç" value="intern" /> |
| | | <el-option label="å®ä¹ ç" value="intern" /> --> |
| | | <el-option |
| | | v-for="option in getTypeOptions()" |
| | | :key="option.value" |
| | | :label="option.label" |
| | | :value="option.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | |
| | | @change="validateTimeField('startTime')" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | |
| | | <el-form-item label="ç»ææ¶é´" prop="endTime" v-if="currentType === 'overtime'"> |
| | | <el-time-picker |
| | | v-model="form.endTime" |
| | |
| | | <el-input-number v-model="form.flexibleMinutes" :min="0" :max="120" style="width: 100%" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="ç¶æ" prop="status"> |
| | | <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 @click="dialogVisible = false">åæ¶</el-button> |
| | |
| | | <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' |
| | | |
| | | // å½åæ¿æ´»çæ ç¾é¡µ |
| | | const activeTab = ref('holiday') |
| | |
| | | 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 attendanceData = ref([]) |
| | | const filteredAttendanceData = ref([]) |
| | | const attendanceDate = ref('') |
| | | const attendanceStatus = ref('') |
| | | |
| | | // è¡¨åæ°æ® |
| | | const form = reactive({ |
| | | name: '', |
| | | type: '', |
| | | dateRange: [], |
| | | startDate: '', |
| | | endDate: '', |
| | | days: 0, |
| | | employeeType: '', |
| | | workYears: '', |
| | |
| | | workYears: [{ required: true, message: '请è¾å
¥å·¥ä½å¹´é', trigger: 'blur' }], |
| | | annualDays: [{ required: true, message: '请è¾å
¥å¹´å天æ°', trigger: 'blur' }], |
| | | maxCarryOver: [{ required: true, message: '请è¾å
¥æå¤§ç»è½¬å¤©æ°', trigger: 'blur' }], |
| | | startTime: [{ |
| | | required: true, |
| | | message: 'è¯·éæ©å¼å§æ¶é´', |
| | | startTime: [{ |
| | | required: true, |
| | | message: 'è¯·éæ©å¼å§æ¶é´', |
| | | trigger: 'change', |
| | | validator: (rule, value, callback) => { |
| | | if (!value) { |
| | |
| | | } |
| | | } |
| | | }], |
| | | endTime: [{ |
| | | required: true, |
| | | message: 'è¯·éæ©ç»ææ¶é´', |
| | | endTime: [{ |
| | | required: true, |
| | | message: 'è¯·éæ©ç»ææ¶é´', |
| | | trigger: 'change', |
| | | validator: (rule, value, callback) => { |
| | | if (!value) { |
| | |
| | | } |
| | | } |
| | | }], |
| | | workStartTime: [{ |
| | | required: true, |
| | | message: 'è¯·éæ©ä¸çæ¶é´', |
| | | workStartTime: [{ |
| | | required: true, |
| | | message: 'è¯·éæ©ä¸çæ¶é´', |
| | | trigger: 'change', |
| | | validator: (rule, value, callback) => { |
| | | if (!value) { |
| | |
| | | } |
| | | } |
| | | }], |
| | | workEndTime: [{ |
| | | required: true, |
| | | message: 'è¯·éæ©ä¸çæ¶é´', |
| | | workEndTime: [{ |
| | | required: true, |
| | | message: 'è¯·éæ©ä¸çæ¶é´', |
| | | trigger: 'change', |
| | | validator: (rule, value, callback) => { |
| | | if (!value) { |
| | |
| | | }], |
| | | rate: [{ required: true, message: '请è¾å
¥åç', trigger: 'blur' }] |
| | | } |
| | | |
| | | // æ¨¡ææ°æ® |
| | | const holidayData = ref([ |
| | | { id: '1', name: 'æ¥è', type: 'legal', startDate: '2024-02-10', endDate: '2024-02-17', days: 8, status: 'active' }, |
| | | { id: '2', name: 'æ¸
æè', type: 'legal', startDate: '2024-04-05', endDate: '2024-04-05', days: 1, status: 'active' }, |
| | | { id: '3', name: 'å³å¨è', type: 'legal', startDate: '2024-05-01', endDate: '2024-05-05', days: 5, status: 'active' } |
| | | ]) |
| | | |
| | | const annualData = ref([ |
| | | { id: '1', employeeType: 'regular', workYears: '1-3å¹´', annualDays: 5, maxCarryOver: 2, status: 'active' }, |
| | | { id: '2', employeeType: 'regular', workYears: '3-5å¹´', annualDays: 10, maxCarryOver: 5, status: 'active' }, |
| | | { id: '3', employeeType: 'regular', workYears: '5年以ä¸', annualDays: 15, maxCarryOver: 10, status: 'active' } |
| | | ]) |
| | | |
| | | const overtimeData = ref([ |
| | | { id: '1', name: '工使¥å ç', type: 'weekday', startTime: '18:00', endTime: '22:00', rate: 1.5, status: 'active' }, |
| | | { id: '2', name: '卿«å ç', type: 'weekend', startTime: '09:00', endTime: '18:00', rate: 2.0, status: 'active' }, |
| | | { id: '3', name: 'æ·±å¤å ç', type: 'night', startTime: '22:00', endTime: '06:00', rate: 2.5, status: 'active' } |
| | | ]) |
| | | |
| | | const worktimeData = ref([ |
| | | { id: '1', name: 'æ å工使¶é´', startTime: '09:00', endTime: '18:00', flexibleStart: true, flexibleMinutes: 30, status: 'active' }, |
| | | { id: '2', name: 'æ©çæ¶é´', startTime: '08:00', endTime: '17:00', flexibleStart: false, flexibleMinutes: 0, status: 'active' }, |
| | | { id: '3', name: 'æçæ¶é´', startTime: '14:00', endTime: '23:00', flexibleStart: false, flexibleMinutes: 0, status: 'active' } |
| | | ]) |
| | | |
| | | // å·¥å
·å½æ° |
| | | const getTagType = (type) => { |
| | | const tagMap = { |
| | | legal: 'success', adjustment: 'warning', special: 'info', company: 'primary', |
| | | weekday: 'primary', weekend: 'warning', holiday: 'danger', night: 'info' |
| | | 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: 'æ·±å¤å ç' |
| | | 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 = () => { |
| | |
| | | { label: 'è忥å ç', value: 'holiday' }, |
| | | { label: 'æ·±å¤å ç', value: 'night' } |
| | | ] |
| | | } else if (currentType.value === 'annual') { |
| | | return [ |
| | | { label: 'æ£å¼åå·¥', value: 'regular' }, |
| | | { label: 'è¯ç¨æåå·¥', value: 'probation' }, |
| | | { label: 'å®ä¹ ç', value: 'intern' } |
| | | ] |
| | | } |
| | | return [] |
| | | } |
| | |
| | | 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 |
| | |
| | | } |
| | | |
| | | // éªè¯æ¶é´æ ¼å¼ |
| | | const validateTime = (time) => { |
| | | if (!time) return '' |
| | | if (typeof time === 'string') return time |
| | | if (time instanceof Date) { |
| | | return time.toTimeString().slice(0, 5) |
| | | } |
| | | return '' |
| | | } |
| | | // 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 { |
| | | currentType.value = type |
| | | currentAction.value = action |
| | | |
| | | |
| | | if (action === 'add') { |
| | | dialogTitle.value = `æ°å¢${getTypeName(type)}` |
| | | currentEditId.value = '' |
| | |
| | | currentEditId.value = row.id |
| | | fillForm(row) |
| | | } |
| | | |
| | | |
| | | dialogVisible.value = true |
| | | } catch (error) { |
| | | console.error('æå¼å¼¹çªå¤±è´¥:', error) |
| | |
| | | name: '', |
| | | type: '', |
| | | dateRange: [], |
| | | startDate: '', |
| | | endDate: '', |
| | | days: 0, |
| | | employeeType: '', |
| | | workYears: '', |
| | |
| | | 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 |
| | | }) |
| | |
| | | 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) { |
| | |
| | | } |
| | | |
| | | const addItem = () => { |
| | | const newItem = { ...form, id: Date.now().toString() } |
| | | |
| | | |
| | | if (currentType.value === 'holiday') { |
| | | newItem.startDate = form.dateRange[0].toISOString().split('T')[0] |
| | | newItem.endDate = form.dateRange[1].toISOString().split('T')[0] |
| | | holidayData.value.push(newItem) |
| | | 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) |
| | | // 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') { |
| | | newItem.startTime = form.startTime || '' |
| | | newItem.endTime = form.endTime || '' |
| | | overtimeData.value.push(newItem) |
| | | 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') { |
| | | newItem.startTime = form.workStartTime || '' |
| | | newItem.endTime = form.workEndTime || '' |
| | | worktimeData.value.push(newItem) |
| | | 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') { |
| | | dataArray = holidayData.value |
| | | index = dataArray.findIndex(item => item.id === currentEditId.value) |
| | | if (index > -1) { |
| | | dataArray[index] = { |
| | | ...dataArray[index], |
| | | 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 |
| | | } |
| | | 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') { |
| | | dataArray = annualData.value |
| | | index = dataArray.findIndex(item => item.id === currentEditId.value) |
| | | if (index > -1) { |
| | | dataArray[index] = { |
| | | ...dataArray[index], |
| | | employeeType: form.employeeType, |
| | | workYears: form.workYears, |
| | | annualDays: form.annualDays, |
| | | maxCarryOver: form.maxCarryOver, |
| | | status: form.status |
| | | } |
| | | 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') { |
| | | 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 |
| | | } |
| | | 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') { |
| | | 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 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 |
| | | } |
| | | |
| | | // å é¤é¡¹ç® |
| | |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | let ids = []; |
| | | let dataArray |
| | | if (type === 'holiday') dataArray = holidayData.value |
| | | else if (type === 'annual') dataArray = annualData.value |
| | | else if (type === 'overtime') dataArray = overtimeData.value |
| | | else if (type === 'worktime') dataArray = worktimeData.value |
| | | |
| | | const index = dataArray.findIndex(item => item.id === row.id) |
| | | if (index > -1) { |
| | | dataArray.splice(index, 1) |
| | | ElMessage.success('å 餿å') |
| | | 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('èå¤ç®¡ç页é¢å è½½å®æ') |
| | | }) |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- 头é¨å¯¼èª --> |
| | | <!-- <div class="header"> |
| | | <h2>ä¼ä¸é讯å½ç®¡ç</h2> |
| | | <p>管ç个人ãå
Œ
±ååä½çèç³»æ¹å¼</p> |
| | | </div> --> |
| | | |
| | | <!-- æ ç¾é¡µåæ¢ --> |
| | | <el-tabs v-model="activeTab" @tab-change="handleTabChange" type="border-card"> |
| | | <el-tab-pane label="个人é讯å½" name="personal"> |
| | | <div class="tab-content"> |
| | | <!-- æç´¢æ¡ --> |
| | | <el-input |
| | | v-model="personalSearch.staffName" |
| | | placeholder="æç´¢è系人" |
| | | clearable |
| | | prefix-icon="Search" |
| | | class="search-input" |
| | | @keyup.enter="getPersonalContactsList" |
| | | /> |
| | | <el-button style="margin: 0 0 20px 20px;" type="primary" @click="showAddContactDialog=true">æ·»å è系人</el-button> |
| | | <!-- è系人å表 --> |
| | | <div class="contact-list"> |
| | | <div |
| | | v-for="contact in personalContacts" |
| | | :key="contact.id" |
| | | class="contact-card" |
| | | @click="showContactDetail(contact)" |
| | | > |
| | | <div class="contact-avatar">{{ contact.staffName.charAt(0) }}</div> |
| | | <div class="contact-info"> |
| | | <h4>{{ contact.staffName }}</h4> |
| | | <p>{{ contact.profession }} - {{ contact.postJob }}</p> |
| | | <div class="contact-phone">{{ contact.phone }}</div> |
| | | </div> |
| | | <div class="contact-actions"> |
| | | <!-- <el-button |
| | | type="text" |
| | | icon="Phone" |
| | | @click.stop="callContact(contact)" |
| | | ></el-button> --> |
| | | <el-button |
| | | type="text" |
| | | icon="Message" |
| | | @click.stop="messageContact(contact)" |
| | | ></el-button> |
| | | <el-button |
| | | type="text" |
| | | icon="Delete" |
| | | @click.stop="removeFromPersonalContacts(contact.id)" |
| | | ></el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- ç©ºç¶æ |
| | | <div v-if="personalContacts.length === 0 && !loading" class="empty-state"> |
| | | <el-empty description="ææ è系人" /> |
| | | <el-button type="primary" @click="showAddContactDialog=true">æ·»å è系人</el-button> |
| | | </div> --> |
| | | </div> |
| | | </div> |
| | | </el-tab-pane> |
| | | |
| | | <el-tab-pane label="å
Œ
±é讯å½" name="public"> |
| | | <div class="tab-content"> |
| | | <!-- æç´¢æ¡ --> |
| | | <el-input |
| | | v-model="publicSearch.staffName" |
| | | placeholder="æç´¢å
Œ
±è系人" |
| | | clearable |
| | | prefix-icon="Search" |
| | | class="search-input" |
| | | @keyup.enter="getPublicContactsList" |
| | | /> |
| | | |
| | | <!-- è系人å表 publicContacts--> |
| | | <div class="contact-list"> |
| | | <div |
| | | v-for="contact in EmployeeList" |
| | | :key="contact.id" |
| | | class="contact-card" |
| | | @click="showContactDetail(contact)" |
| | | > |
| | | <div class="contact-avatar">{{ contact.staffName.charAt(0) }}</div> |
| | | <div class="contact-info"> |
| | | <h4>{{ contact.staffName }}</h4> |
| | | <p>{{ contact.postJob }} - {{ contact.profession }}</p> |
| | | <div class="contact-phone">{{ contact.phone }}</div> |
| | | </div> |
| | | <div class="contact-actions"> |
| | | <!-- <el-button |
| | | type="text" |
| | | icon="Phone" |
| | | @click.stop="callContact(contact)" |
| | | ></el-button> --> |
| | | <el-button |
| | | type="text" |
| | | icon="Message" |
| | | @click.stop="messageContact(contact)" |
| | | ></el-button> |
| | | <el-button |
| | | type="text" |
| | | icon="Delete" |
| | | :type="isInPersonalContacts(contact.id) ? 'primary' : ''" |
| | | @click.stop="togglePersonalContact(contact)" |
| | | ></el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-tab-pane> |
| | | |
| | | <el-tab-pane label="åä½é讯å½" name="company"> |
| | | <div class="tab-content"> |
| | | <div class="company-contacts-layout"> |
| | | <!-- 左侧é¨é¨æ --> |
| | | <div class="department-tree"> |
| | | <!-- <h3>é¨é¨ç»æ</h3> |
| | | <el-tree |
| | | :data="departmentTree" |
| | | :props="{ label: 'deptName', children: 'children' }" |
| | | node-key="deptId" |
| | | ref="departmentTreeRef" |
| | | highlight-current |
| | | default-expand-all |
| | | @node-click="handleDepartmentClick" |
| | | /> --> |
| | | <el-col > |
| | | <div class="head-container"> |
| | | <el-input |
| | | v-model="deptName" |
| | | placeholder="请è¾å
¥é¨é¨åç§°" |
| | | clearable |
| | | prefix-icon="Search" |
| | | style="margin-bottom: 20px" |
| | | /> |
| | | </div> |
| | | <div class="head-container"> |
| | | <el-tree |
| | | :data="departmentTree" |
| | | :props="{ label: 'label', children: 'children' }" |
| | | :expand-on-click-node="false" |
| | | :filter-node-method="filterNode" |
| | | ref="deptTreeRef" |
| | | node-key="id" |
| | | highlight-current |
| | | default-expand-all |
| | | @node-click="handleDepartmentClick" |
| | | /> |
| | | </div> |
| | | </el-col> |
| | | </div> |
| | | |
| | | <!-- å³ä¾§é¨é¨æå --> |
| | | <div class="department-members"> |
| | | <h3>{{ currentDepartment?.label || 'å
¨é¨æå' }}</h3> |
| | | <el-input |
| | | v-model="companySearch.staffName" |
| | | placeholder="æç´¢é¨é¨æå" |
| | | clearable |
| | | prefix-icon="Search" |
| | | class="search-input" |
| | | @keyup.enter="getCompanyContactsList" |
| | | /> |
| | | |
| | | <div class="contact-list"> |
| | | <div |
| | | v-for="contact in companyContacts" |
| | | :key="contact.id" |
| | | class="contact-card" |
| | | @click="showContactDetail(contact)" |
| | | > |
| | | <div class="contact-avatar">{{ contact.staffName.charAt(0) }}</div> |
| | | <div class="contact-info"> |
| | | <h4>{{ contact.staffName }}</h4> |
| | | <p>{{ contact.profession }}</p> |
| | | <div class="contact-phone">{{ contact.phone }}</div> |
| | | </div> |
| | | <div class="contact-actions"> |
| | | |
| | | <el-button |
| | | type="text" |
| | | icon="Message" |
| | | @click.stop="messageContact(contact)" |
| | | ></el-button> |
| | | <el-button |
| | | type="text" |
| | | icon="Delete" |
| | | :type="isInPersonalContacts(contact.id) ? 'primary' : ''" |
| | | @click.stop="togglePersonalContact(contact)" |
| | | ></el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | |
| | | <!-- è系人详æ
å¼¹çª --> |
| | | <el-dialog |
| | | v-model="showDetailDialog" |
| | | title="è系人详æ
" |
| | | width="400px" |
| | | > |
| | | <div v-if="selectedContact" class="contact-detail"> |
| | | <div class="detail-avatar">{{ selectedContact.staffName?.charAt(0) }}</div> |
| | | <h3>{{ selectedContact.staffName }}</h3> |
| | | <p class="detail-position">{{ selectedContact.profession }} - {{ selectedContact.postJob }}</p> |
| | | |
| | | <div class="detail-info"> |
| | | <div class="info-item"> |
| | | <span class="label">ç¼å·ï¼</span> |
| | | <span class="value">{{ selectedContact.staffNo }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="label">ææºå·ç ï¼</span> |
| | | <span class="value">{{ selectedContact.phone }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="label">é®ç®±ï¼</span> |
| | | <span class="value">{{ selectedContact.sex }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="label">ä½åï¼</span> |
| | | <span class="value">{{ selectedContact.adress || 'ææ ' }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <template #footer> |
| | | <el-button @click="showDetailDialog = false">å
³é</el-button> |
| | | <el-button |
| | | type="primary" |
| | | v-if="activeTab !== 'personal'" |
| | | @click="togglePersonalContact(selectedContact); showDetailDialog = false" |
| | | > |
| | | {{ isInPersonalContacts(selectedContact?.id) ? 'ä»ä¸ªäººé讯å½ç§»é¤' : 'æ·»å å°ä¸ªäººé讯å½' }} |
| | | </el-button> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- æ·»å èç³»äººå¼¹çª --> |
| | | <el-dialog |
| | | v-model="showAddContactDialog" |
| | | title="æ·»å è系人" |
| | | width="500px" |
| | | > |
| | | <el-form :model="addContactForm" ref="addContactFormRef" label-width="80px"> |
| | | <!-- <el-form-item label="å§å" prop="name"> |
| | | <el-input v-model="addContactForm.name" placeholder="请è¾å
¥å§å" /> |
| | | </el-form-item> |
| | | <el-form-item label="ææºå·ç " prop="phone"> |
| | | <el-input v-model="addContactForm.phone" placeholder="请è¾å
¥ææºå·ç " /> |
| | | </el-form-item> |
| | | <el-form-item label="é®ç®±" prop="email"> |
| | | <el-input v-model="addContactForm.email" placeholder="请è¾å
¥é®ç®±" /> |
| | | </el-form-item> |
| | | <el-form-item label="é¨é¨" prop="department"> |
| | | <el-input v-model="addContactForm.department" placeholder="请è¾å
¥é¨é¨" /> |
| | | </el-form-item> --> |
| | | <el-form-item label="å§å" prop="name"> |
| | | <!-- <select v-model="addContactForm.contactId"> |
| | | <option v-for="item in EmployeeList" :key="item.id" :value="item.id">{{ item.staffName }}</option> |
| | | </select> --> |
| | | <el-select v-model="addContactForm.contactId" placeholder="è¯·éæ©" style="width: 100%"> |
| | | <el-option |
| | | v-for="option in EmployeeList" |
| | | :key="option.id" |
| | | :label="option.staffName" |
| | | :value="option.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="showAddContactDialog = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="addContact">ç¡®å®</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, reactive, computed } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { |
| | | getPersonalContacts, |
| | | addPersonalContact, |
| | | removePersonalContact, |
| | | getPublicContacts, |
| | | getCompanyContacts, |
| | | getDepartmentTree, |
| | | getEmployeeDetail |
| | | } from '@/api/collaborativeApproval/enterpriseBook.js' |
| | | import { getUserProfile } from '@/api/system/user.js' |
| | | import {staffJoinListPage} from "@/api/personnelManagement/onboarding.js"; |
| | | import { |
| | | changeUserStatus, |
| | | listUser, |
| | | resetUserPwd, |
| | | delUser, |
| | | getUser, |
| | | updateUser, |
| | | addUser, |
| | | deptTreeSelect, |
| | | } from "@/api/system/user"; |
| | | |
| | | // æ ç¾é¡µç¶æ |
| | | const activeTab = ref('personal') |
| | | const loading = ref(false) |
| | | const EmployeeList = ref([]) |
| | | const page = reactive({ |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }) |
| | | // 个人éè®¯å½æ°æ® |
| | | const personalContacts = ref([]) |
| | | const personalSearch = ref({ |
| | | staffName: '', |
| | | }) |
| | | |
| | | // å
Œ
±éè®¯å½æ°æ® |
| | | const publicContacts = ref([]) |
| | | const publicSearch = ref({ |
| | | staffName: '', |
| | | staffState: 1 |
| | | }) |
| | | |
| | | // åä½éè®¯å½æ°æ® |
| | | const companyContacts = ref([]) |
| | | const companySearch = ref({ |
| | | staffName: '', |
| | | staffState: 1 |
| | | }) |
| | | const departmentTree = ref([]) |
| | | const departmentTreeRef = ref(null) |
| | | const currentDepartment = ref(null) |
| | | |
| | | // å¼¹çªç¶æ |
| | | const showDetailDialog = ref(false) |
| | | const showAddContactDialog = ref(false) |
| | | const selectedContact = ref(null) |
| | | |
| | | // æ·»å è系人表å |
| | | const addContactForm = reactive({ |
| | | contactId: '', |
| | | name: '', |
| | | phone: '', |
| | | email: '', |
| | | department: '', |
| | | position: '' |
| | | }) |
| | | const addContactFormRef = ref(null) |
| | | |
| | | // åå§åæ°æ® |
| | | onMounted(() => { |
| | | getEmployeeList() |
| | | getPersonalContactsList() |
| | | if (activeTab.value === 'public') { |
| | | getPublicContactsList() |
| | | } else if (activeTab.value === 'company') { |
| | | getDepartmentTreeData() |
| | | getCompanyContactsList() |
| | | } |
| | | }) |
| | | |
| | | // å¤çæ ç¾é¡µåæ¢ |
| | | const handleTabChange = (tabName) => { |
| | | if (tabName === 'public') { |
| | | getPublicContactsList() |
| | | } else if (tabName === 'company') { |
| | | getDepartmentTreeData() |
| | | getCompanyContactsList() |
| | | } |
| | | } |
| | | |
| | | // è·å个人é讯å½å表 |
| | | const getPersonalContactsList = async () => { |
| | | loading.value = true |
| | | getPersonalContacts(page,personalSearch.value).then(res => { |
| | | personalContacts.value = res.data.records |
| | | }) |
| | | loading.value = false |
| | | } |
| | | |
| | | // è·åå
Œ
±é讯å½å表 |
| | | const getPublicContactsList = async () => { |
| | | loading.value = true |
| | | getEmployeeList() |
| | | // publicContacts.value = generateMockPublicContacts() |
| | | loading.value = false |
| | | } |
| | | //è·ååå·¥å表 |
| | | const getEmployeeList = async () => { |
| | | staffJoinListPage(publicSearch.value).then(res => { |
| | | console.log(res.data.records) |
| | | EmployeeList.value = res.data.records |
| | | }).catch(err => {}) |
| | | } |
| | | // è·ååä½é讯å½å表 |
| | | const getCompanyContactsList = async () => { |
| | | loading.value = true |
| | | staffJoinListPage(companySearch.value).then(res => { |
| | | // console.log(res.data.records) |
| | | companyContacts.value = res.data.records |
| | | }).catch(err => {}) |
| | | |
| | | loading.value = false |
| | | loading.value = false |
| | | // } |
| | | } |
| | | |
| | | // è·åé¨é¨æ ç»æ |
| | | const getDepartmentTreeData = async () => { |
| | | deptTreeSelect().then((response) => { |
| | | // console.log("Tree",response.data) |
| | | departmentTree.value = response.data; |
| | | // enabledDeptOptions.value = filterDisabledDept( |
| | | // JSON.parse(JSON.stringify(response.data)) |
| | | // ); |
| | | }); |
| | | } |
| | | // /** è¿æ»¤ç¦ç¨çé¨é¨ */ |
| | | // function filterDisabledDept(deptList) { |
| | | // return deptList.filter((dept) => { |
| | | // if (dept.disabled) { |
| | | // return false; |
| | | // } |
| | | // if (dept.children && dept.children.length) { |
| | | // dept.children = filterDisabledDept(dept.children); |
| | | // } |
| | | // return true; |
| | | // }); |
| | | // } |
| | | // å¤çé¨é¨ç¹å» |
| | | const handleDepartmentClick = (data) => { |
| | | // console.log("ç¹å»",data) |
| | | companySearch.value = { |
| | | ...companySearch.value, |
| | | deptId: data.id, |
| | | } |
| | | // currentDepartment.value = data.id |
| | | // è·å该é¨é¨çæåå表 |
| | | |
| | | getCompanyContactsList() |
| | | } |
| | | |
| | | // æ¾ç¤ºè系人详æ
|
| | | const showContactDetail = async (contact) => { |
| | | selectedContact.value = contact |
| | | showDetailDialog.value = true |
| | | } |
| | | |
| | | // æ¨æçµè¯ |
| | | const callContact = (contact) => { |
| | | ElMessage.info(`æ£å¨æ¨æ ${contact.name} ççµè¯: ${contact.phone}`) |
| | | } |
| | | |
| | | // åéæ¶æ¯ |
| | | const messageContact = (contact) => { |
| | | ElMessage.info(`æ£å¨åéæ¶æ¯ç» ${contact.name}`) |
| | | } |
| | | |
| | | |
| | | // æ·»å è系人 |
| | | const addContact = async () => { |
| | | |
| | | try { |
| | | // 表åéªè¯ |
| | | // if (!addContactForm.name || !addContactForm.phone) { |
| | | // ElMessage.warning('请填åå§ååææºå·ç ') |
| | | // return |
| | | // } |
| | | |
| | | const res = await addPersonalContact(addContactForm) |
| | | if (res.code === 200) { |
| | | ElMessage.success('æ·»å æå') |
| | | showAddContactDialog.value = false |
| | | getPersonalContactsList() |
| | | // é置表å |
| | | Object.keys(addContactForm).forEach(key => { |
| | | addContactForm[key] = '' |
| | | }) |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error('æ·»å 失败') |
| | | // æ¨¡ææ·»å æå |
| | | personalContacts.value.push({ |
| | | ...addContactForm, |
| | | id: Date.now(), |
| | | createTime: new Date().toISOString() |
| | | }) |
| | | ElMessage.success('æ·»å æå') |
| | | showAddContactDialog.value = false |
| | | // é置表å |
| | | Object.keys(addContactForm).forEach(key => { |
| | | addContactForm[key] = '' |
| | | }) |
| | | } |
| | | } |
| | | |
| | | // ä»ä¸ªäººé讯å½ç§»é¤ |
| | | const removeFromPersonalContacts = async (contactId) => { |
| | | ElMessageBox.confirm( |
| | | 'ç¡®å®è¦ä»ä¸ªäººé讯å½ä¸ç§»é¤è¯¥è系人åï¼', |
| | | 'æç¤º', |
| | | { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | } |
| | | ).then(async () => { |
| | | try { |
| | | const res = await removePersonalContact(contactId) |
| | | if (res.code === 200) { |
| | | ElMessage.success('ç§»é¤æå') |
| | | getPersonalContactsList() |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error('ç§»é¤å¤±è´¥') |
| | | // 模æç§»é¤æå |
| | | // personalContacts.value = personalContacts.value.filter(item => item.id !== contactId) |
| | | ElMessage.success('ç§»é¤æå') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // åæ¢ä¸ªäººéè®¯å½ |
| | | const togglePersonalContact = async (contact) => { |
| | | const isInPersonal = isInPersonalContacts(contact.id) |
| | | const contactId = contact.id |
| | | if (isInPersonal) { |
| | | // ä»ä¸ªäººé讯å½ç§»é¤ |
| | | //æ ¹æ®contactIdæ¥æ¾personalContactsä¸å¯¹åºç项ï¼ç¶åå é¤è¯¥é¡¹ |
| | | const index = personalContacts.value.findIndex(item => item.contactId === contactId) |
| | | const personId = personalContacts.value[index].id |
| | | // console.log(personId) |
| | | await removeFromPersonalContacts(personId) |
| | | } else { |
| | | // æ·»å å°ä¸ªäººéè®¯å½ |
| | | try { |
| | | const res = await addPersonalContact({contactId: contactId}) |
| | | if (res.code === 200) { |
| | | ElMessage.success('æ·»å æå') |
| | | getPersonalContactsList() |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error('æ·»å 失败') |
| | | // æ¨¡ææ·»å æå |
| | | // personalContacts.value.push({ |
| | | // ...contact, |
| | | // id: contact.id || Date.now(), |
| | | // createTime: new Date().toISOString() |
| | | // }) |
| | | // ElMessage.success('æ·»å æå') |
| | | } |
| | | } |
| | | } |
| | | |
| | | // æ£æ¥æ¯å¦å¨ä¸ªäººé讯å½ä¸ |
| | | const isInPersonalContacts = (contactId) => { |
| | | return personalContacts.value.some(item => item.contactId === contactId) |
| | | } |
| | | |
| | | // çææ¨¡æé¨é¨æ æ°æ® |
| | | const generateMockDepartmentTree = () => { |
| | | return [ |
| | | { |
| | | deptId: 1, |
| | | deptName: 'ææ¯é¨', |
| | | children: [ |
| | | { |
| | | deptId: 101, |
| | | deptName: 'å端ç»' |
| | | }, |
| | | { |
| | | deptId: 102, |
| | | deptName: 'å端ç»' |
| | | }, |
| | | { |
| | | deptId: 103, |
| | | deptName: 'æµè¯ç»' |
| | | } |
| | | ] |
| | | }, |
| | | { |
| | | deptId: 2, |
| | | deptName: '产åé¨' |
| | | }, |
| | | { |
| | | deptId: 3, |
| | | deptName: '人äºé¨' |
| | | }, |
| | | { |
| | | deptId: 4, |
| | | deptName: 'è´¢å¡é¨' |
| | | } |
| | | ] |
| | | } |
| | | |
| | | // çææ¨¡æåä½éè®¯å½æ°æ® |
| | | // const generateMockCompanyContacts = (deptName) => { |
| | | // const allContacts = getEmployeeList() |
| | | |
| | | // if (deptName) { |
| | | // return allContacts.filter(contact => contact.postJob === deptName) |
| | | // } |
| | | // return allContacts |
| | | // } |
| | | |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .header { |
| | | margin-bottom: 20px; |
| | | padding: 15px; |
| | | background: #f5f7fa; |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .header h2 { |
| | | margin: 0 0 5px 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .header p { |
| | | margin: 0; |
| | | color: #909399; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .tab-content { |
| | | padding: 15px; |
| | | background: #fff; |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); |
| | | } |
| | | |
| | | .search-input { |
| | | margin-bottom: 20px; |
| | | width: 300px; |
| | | } |
| | | |
| | | .contact-list { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 15px; |
| | | } |
| | | |
| | | .contact-card { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 15px; |
| | | width: 500px; |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | cursor: pointer; |
| | | transition: all 0.3s; |
| | | } |
| | | |
| | | .contact-card:hover { |
| | | background: #e9ecef; |
| | | transform: translateY(-2px); |
| | | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .contact-avatar { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | width: 48px; |
| | | height: 48px; |
| | | background: #409eff; |
| | | color: #fff; |
| | | font-size: 20px; |
| | | font-weight: bold; |
| | | border-radius: 50%; |
| | | margin-right: 15px; |
| | | } |
| | | |
| | | .contact-info { |
| | | flex: 1; |
| | | } |
| | | |
| | | .contact-info h4 { |
| | | margin: 0 0 5px 0; |
| | | color: #303133; |
| | | font-size: 16px; |
| | | } |
| | | |
| | | .contact-info p { |
| | | margin: 0 0 5px 0; |
| | | color: #606266; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .contact-phone { |
| | | color: #409eff; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .contact-actions { |
| | | display: flex; |
| | | gap: 5px; |
| | | } |
| | | |
| | | .empty-state { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 60px 20px; |
| | | color: #909399; |
| | | } |
| | | |
| | | .company-contacts-layout { |
| | | display: flex; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .department-tree { |
| | | width: 250px; |
| | | padding: 15px; |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .department-tree h3 { |
| | | margin: 0 0 15px 0; |
| | | padding-bottom: 10px; |
| | | border-bottom: 1px solid #e4e7ed; |
| | | color: #303133; |
| | | font-size: 16px; |
| | | } |
| | | |
| | | .department-members { |
| | | flex: 1; |
| | | } |
| | | |
| | | .department-members h3 { |
| | | margin: 0 0 15px 0; |
| | | color: #303133; |
| | | font-size: 16px; |
| | | } |
| | | |
| | | .contact-detail { |
| | | text-align: center; |
| | | } |
| | | |
| | | .detail-avatar { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | width: 80px; |
| | | height: 80px; |
| | | background: #409eff; |
| | | color: #fff; |
| | | font-size: 32px; |
| | | font-weight: bold; |
| | | border-radius: 50%; |
| | | margin: 0 auto 20px; |
| | | } |
| | | |
| | | .contact-detail h3 { |
| | | margin: 0 0 10px 0; |
| | | color: #303133; |
| | | font-size: 20px; |
| | | } |
| | | |
| | | .detail-position { |
| | | margin: 0 0 30px 0; |
| | | color: #606266; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .detail-info { |
| | | text-align: left; |
| | | } |
| | | |
| | | .info-item { |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .info-item .label { |
| | | display: inline-block; |
| | | width: 100px; |
| | | color: #909399; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .info-item .value { |
| | | color: #303133; |
| | | font-size: 14px; |
| | | } |
| | | </style> |
| | |
| | | /> |
| | | <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-option |
| | | v-for="item in knowledgeTypeOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | <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> |
| | | </div> |
| | | </div> |
| | | |
| | | |
| | | <div class="table_list"> |
| | | <PIMTable |
| | | rowKey="id" |
| | |
| | | <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-option |
| | | v-for="item in knowledgeTypeOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | <span class="dialog-footer"> |
| | | <el-button @click="viewDialogVisible = false">å
³é</el-button> |
| | | <el-button type="primary" @click="copyKnowledge">å¤å¶ç¥è¯</el-button> |
| | | <el-button type="success" @click="markAsFavorite">æ¶è</el-button> |
| | | <!-- <el-button type="success" @click="markAsFavorite">æ¶è@</el-button> --> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | |
| | | <script setup> |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { onMounted, ref, reactive, toRefs } from "vue"; |
| | | import { onMounted, ref, reactive, toRefs, getCurrentInstance, computed } 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 = { |
| | |
| | | tableLoading: false, |
| | | page: { |
| | | current: 1, |
| | | size: 100, |
| | | size: 20, |
| | | total: 0, |
| | | }, |
| | | tableData: [], |
| | |
| | | title: "", |
| | | type: "", |
| | | scenario: "", |
| | | efficiency: "medium", |
| | | efficiency: "", |
| | | problem: "", |
| | | solution: "", |
| | | keyPoints: "", |
| | |
| | | currentKnowledge: {} |
| | | }); |
| | | |
| | | const { |
| | | searchForm, |
| | | tableLoading, |
| | | page, |
| | | tableData, |
| | | const { |
| | | searchForm, |
| | | tableLoading, |
| | | page, |
| | | tableData, |
| | | selectedIds, |
| | | form, |
| | | dialogVisible, |
| | |
| | | prop: "type", |
| | | dataType: "tag", |
| | | formatData: (params) => { |
| | | const typeMap = { |
| | | contract: "ååç¹æ¹", |
| | | approval: "å®¡æ¹æ¡ä¾", |
| | | solution: "è§£å³æ¹æ¡", |
| | | experience: "ç»éªæ»ç»", |
| | | guide: "æä½æå" |
| | | }; |
| | | return typeMap[params] || params; |
| | | return getKnowledgeTypeLabel(params); |
| | | }, |
| | | formatType: (params) => { |
| | | const typeMap = { |
| | | contract: "success", |
| | | approval: "warning", |
| | | solution: "primary", |
| | | experience: "info", |
| | | guide: "danger" |
| | | }; |
| | | return typeMap[params] || "info"; |
| | | return getKnowledgeTypeTagType(params); |
| | | } |
| | | }, |
| | | { |
| | |
| | | } |
| | | ]); |
| | | |
| | | // æ¨¡ææ°æ® |
| | | 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); |
| | | } |
| | | |
| | | console.log(`[${new Date().toLocaleString()}] èªå¨çææ°ç¥è¯: ${title}`); |
| | | }; |
| | | |
| | | // çå½å¨æ |
| | | onMounted(() => { |
| | | getList(); |
| | |
| | | // å¼å§èªå¨å·æ° |
| | | const startAutoRefresh = () => { |
| | | setInterval(() => { |
| | | generateNewData(); |
| | | getList(); |
| | | }, 600000); // 10åéå·æ°ä¸æ¬¡ (10 * 60 * 1000 = 600000ms) |
| | | }; |
| | |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | |
| | | setTimeout(() => { |
| | | let filteredData = [...mockData]; |
| | | |
| | | if (searchForm.value.title) { |
| | | filteredData = filteredData.filter(item => |
| | | item.title.toLowerCase().includes(searchForm.value.title.toLowerCase()) |
| | | ); |
| | | } |
| | | |
| | | if (searchForm.value.type) { |
| | | filteredData = filteredData.filter(item => item.type === searchForm.value.type); |
| | | } |
| | | |
| | | tableData.value = filteredData; |
| | | page.value.total = filteredData.length; |
| | | listKnowledgeBase({...page.value, ...searchForm.value}) |
| | | .then(res => { |
| | | tableLoading.value = false; |
| | | }, 500); |
| | | tableData.value = res.data.records |
| | | page.total = res.data.total; |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | }) |
| | | }; |
| | | |
| | | // å页å¤ç |
| | |
| | | title: "", |
| | | type: "", |
| | | scenario: "", |
| | | efficiency: "medium", |
| | | efficiency: "", |
| | | problem: "", |
| | | solution: "", |
| | | keyPoints: "", |
| | |
| | | } else if (type === "edit" && row) { |
| | | dialogTitle.value = "ç¼è¾ç¥è¯"; |
| | | Object.assign(form.value, { |
| | | id: row.id, |
| | | title: row.title, |
| | | type: row.type, |
| | | scenario: row.scenario, |
| | |
| | | |
| | | // è·åç±»åæ ç¾ææ¬ |
| | | const getTypeLabel = (type) => { |
| | | const typeMap = { |
| | | contract: "ååç¹æ¹", |
| | | approval: "å®¡æ¹æ¡ä¾", |
| | | solution: "è§£å³æ¹æ¡", |
| | | experience: "ç»éªæ»ç»", |
| | | guide: "æä½æå" |
| | | }; |
| | | return typeMap[type] || type; |
| | | return getKnowledgeTypeLabel(type); |
| | | }; |
| | | |
| | | // è·åæçæ ç¾ç±»å |
| | |
| | | // å¤å¶ç¥è¯ |
| | | 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} |
| | | ç¥è¯æ é¢ï¼${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("ç¥è¯å
容已å¤å¶å°åªè´´æ¿"); |
| | |
| | | }); |
| | | }; |
| | | |
| | | // æ¶èç¥è¯ |
| | | 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") { |
| | | // æ°å¢ç¥è¯ |
| | | const newKnowledge = { |
| | | id: (mockData.length + 1).toString(), |
| | | title: form.value.title, |
| | | type: form.value.type, |
| | | scenario: form.value.scenario, |
| | | efficiency: form.value.efficiency, |
| | | problem: form.value.problem, |
| | | solution: form.value.solution, |
| | | keyPoints: form.value.keyPoints, |
| | | creator: form.value.creator, |
| | | usageCount: form.value.usageCount, |
| | | createTime: new Date().toLocaleString() |
| | | }; |
| | | |
| | | mockData.unshift(newKnowledge); |
| | | ElMessage.success("ç¥è¯å建æå"); |
| | | addKnowledgeBase({...form.value}).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("æ·»å æå"); |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | } else { |
| | | // ç¼è¾ç¥è¯ |
| | | const index = mockData.findIndex(item => item.id === selectedIds.value[0]); |
| | | if (index !== -1) { |
| | | Object.assign(mockData[index], { |
| | | title: form.value.title, |
| | | type: form.value.type, |
| | | scenario: form.value.scenario, |
| | | efficiency: form.value.efficiency, |
| | | problem: form.value.problem, |
| | | solution: form.value.solution, |
| | | keyPoints: form.value.keyPoints, |
| | | creator: form.value.creator, |
| | | usageCount: form.value.usageCount |
| | | }); |
| | | ElMessage.success("ç¥è¯æ´æ°æå"); |
| | | } |
| | | updateKnowledgeBase({...form.value}).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("æ´æ°æå"); |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | } |
| | | |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | } catch (error) { |
| | | console.error("表åéªè¯å¤±è´¥:", error); |
| | | } |
| | |
| | | ElMessage.warning("è¯·éæ©è¦å é¤çç¥è¯"); |
| | | return; |
| | | } |
| | | |
| | | |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | // ä»mockDataä¸å é¤éä¸ç项 |
| | | selectedIds.value.forEach(id => { |
| | | const index = mockData.findIndex(item => item.id === id); |
| | | if (index !== -1) { |
| | | mockData.splice(index, 1); |
| | | // console.log(selectedIds.value); |
| | | delKnowledgeBase(selectedIds.value).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("å 餿å"); |
| | | selectedIds.value = []; |
| | | getList(); |
| | | } |
| | | }); |
| | | |
| | | ElMessage.success("å 餿å"); |
| | | selectedIds.value = []; |
| | | getList(); |
| | | }) |
| | | }).catch(() => { |
| | | // ç¨æ·åæ¶ |
| | | }); |
| | | }; |
| | | |
| | | // å¯¼åº |
| | | const { proxy } = getCurrentInstance() |
| | | const { knowledge_type } = proxy.useDict("knowledge_type") |
| | | |
| | | // åå
¸å·¥å
· |
| | | const knowledgeTypeOptions = computed(() => knowledge_type?.value || []) |
| | | const getKnowledgeTypeLabel = (val) => { |
| | | const item = knowledgeTypeOptions.value.find(i => String(i.value) === String(val)) |
| | | return item ? item.label : val |
| | | } |
| | | const getKnowledgeTypeTagType = (val) => { |
| | | const item = knowledgeTypeOptions.value.find(i => String(i.value) === String(val)) |
| | | return item?.elTagType || "info" |
| | | } |
| | | const handleExport = () => { |
| | | proxy.download('/knowledgeBase/export', { ...searchForm.value }, 'ç¥è¯åº.xlsx') |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | |
| | | </el-card> |
| | | <el-card class="stat-card"> |
| | | <div class="stat-content"> |
| | | <div class="stat-number">{{ stats.ongoing }}</div> |
| | | <div class="stat-number">{{ stats.underWay }}</div> |
| | | <div class="stat-label">è¿è¡ä¸</div> |
| | | </div> |
| | | </el-card> |
| | |
| | | </el-card> |
| | | <el-card class="stat-card"> |
| | | <div class="stat-content"> |
| | | <div class="stat-number">{{ stats.upcoming }}</div> |
| | | <div class="stat-number">{{ stats.toStart }}</div> |
| | | <div class="stat-label">å³å°å¼å§</div> |
| | | </div> |
| | | </el-card> |
| | |
| | | </el-tag> |
| | | </div> |
| | | <div class="meeting-time"> |
| | | <el-icon><Clock /></el-icon> |
| | | {{ formatTime(meeting.startTime) }} - {{ formatTime(meeting.endTime) }} |
| | | {{dayjs(meeting.startTime).format("YYYY-MM-DD")}}<el-icon><Clock /></el-icon> |
| | | {{ formatTime(meeting.startTime) }} - {{ formatTime(meeting.endTime) }} |
| | | </div> |
| | | </div> |
| | | |
| | | |
| | | <div class="meeting-info"> |
| | | <div class="info-item"> |
| | | <el-icon><Location /></el-icon> |
| | |
| | | </div> |
| | | |
| | | <div class="meeting-agenda"> |
| | | <h4>è®®ç¨å®æ</h4> |
| | | <h4>ä¼è®®çºªè¦</h4> |
| | | <div class="agenda-list"> |
| | | <div |
| | | v-for="(agenda, index) in meeting.agenda" |
| | | :key="index" |
| | | class="agenda-item" |
| | | :class="{ 'active': agenda.status === 'active', 'completed': agenda.status === 'completed' }" |
| | | > |
| | | <span class="agenda-time">{{ agenda.time }}</span> |
| | | <span class="agenda-content">{{ agenda.content }}</span> |
| | | <el-tag |
| | | :type="getAgendaStatusType(agenda.status)" |
| | | size="small" |
| | | > |
| | | {{ getAgendaStatusText(agenda.status) }} |
| | | </el-tag> |
| | | <div class="editor-container"> |
| | | <div |
| | | v-html="meeting.content" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- <div class="meeting-actions">--> |
| | | <!-- <el-button type="primary" size="small" @click="joinMeeting(meeting)">--> |
| | | <!-- å å
¥ä¼è®®--> |
| | | <!-- </el-button>--> |
| | | <!-- <el-button type="info" size="small" @click="viewDetails(meeting)">--> |
| | | <!-- æ¥ç详æ
--> |
| | | <!-- </el-button>--> |
| | | <!-- <el-button type="warning" size="small" @click="editMeeting(meeting)">--> |
| | | <!-- ç¼è¾--> |
| | | <!-- </el-button>--> |
| | | <!-- </div>--> |
| | | </el-card> |
| | | </div> |
| | | |
| | | <!-- å建ä¼è®®å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="dialogVisible" title="å建ä¼è®®" width="600px"> |
| | | <el-form :model="meetingForm" label-width="100px"> |
| | | <el-form-item label="ä¼è®®æ é¢"> |
| | | <el-input v-model="meetingForm.title" placeholder="请è¾å
¥ä¼è®®æ é¢" /> |
| | | </el-form-item> |
| | | <el-form-item label="ä¼è®®æ¶é´"> |
| | | <el-date-picker |
| | | v-model="meetingForm.timeRange" |
| | | type="datetimerange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¶é´" |
| | | end-placeholder="ç»ææ¶é´" |
| | | format="YYYY-MM-DD HH:mm" |
| | | value-format="YYYY-MM-DD HH:mm:ss" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="ä¼è®®å°ç¹"> |
| | | <el-input v-model="meetingForm.location" placeholder="请è¾å
¥ä¼è®®å°ç¹" /> |
| | | </el-form-item> |
| | | <el-form-item label="主æäºº"> |
| | | <el-input v-model="meetingForm.host" placeholder="请è¾å
¥ä¸»æäººå§å" /> |
| | | </el-form-item> |
| | | <el-form-item label="ä¼è®®æè¿°"> |
| | | <el-input |
| | | v-model="meetingForm.description" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请è¾å
¥ä¼è®®æè¿°" |
| | | /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitMeeting">ç¡®å®</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | import { ref, reactive, onMounted } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { Clock, Location, User, UserFilled } from '@element-plus/icons-vue' |
| | | import Editor from "@/components/Editor/index.vue"; |
| | | import {getMeetSummaryItems,getMeetSummary} from '@/api/collaborativeApproval/meeting.js' |
| | | import dayjs from "dayjs"; |
| | | |
| | | // ç»è®¡æ°æ® |
| | | const stats = reactive({ |
| | | total: 12, |
| | | ongoing: 3, |
| | | completed: 7, |
| | | upcoming: 2 |
| | | const stats = ref({ |
| | | total: 0, |
| | | underWay: 0, |
| | | completed: 0, |
| | | toStart: 0 |
| | | }) |
| | | |
| | | // ä¼è®®æ°æ® |
| | | const meetings = ref([ |
| | | { |
| | | id: 1, |
| | | title: '产åå¼åå¨ä¼', |
| | | status: 'ongoing', |
| | | startTime: '2024-01-15 09:00:00', |
| | | endTime: '2024-01-15 10:30:00', |
| | | location: 'ä¼è®®å®¤A', |
| | | host: 'å¼ ç»ç', |
| | | participants: ['å¼ ç»ç', 'æå·¥ç¨å¸', 'ç设计å¸', 'èµµæµè¯å'], |
| | | agenda: [ |
| | | { time: '09:00-09:15', content: 'ä¸å¨å·¥ä½æ»ç»', status: 'completed' }, |
| | | { time: '09:15-09:45', content: 'æ¬å¨å¼å计å', status: 'active' }, |
| | | { time: '09:45-10:00', content: 'ææ¯é¾ç¹è®¨è®º', status: 'pending' }, |
| | | { time: '10:00-10:30', content: 'é®é¢åé¦ä¸è§£å³', status: 'pending' } |
| | | ] |
| | | }, |
| | | { |
| | | id: 2, |
| | | title: '客æ·éæ±è¯å®¡ä¼', |
| | | status: 'upcoming', |
| | | startTime: '2024-01-15 14:00:00', |
| | | endTime: '2024-01-15 15:00:00', |
| | | location: '线ä¸ä¼è®®', |
| | | host: 'éæ»ç', |
| | | participants: ['éæ»ç', 'å产åç»ç', 'å客æ·ç»ç', '客æ·ä»£è¡¨'], |
| | | agenda: [ |
| | | { time: '14:00-14:20', content: 'éæ±èæ¯ä»ç»', status: 'pending' }, |
| | | { time: '14:20-14:40', content: 'åè½éæ±åæ', status: 'pending' }, |
| | | { time: '14:40-15:00', content: 'ææ¯å¯è¡æ§è¯ä¼°', status: 'pending' } |
| | | ] |
| | | }, |
| | | { |
| | | id: 3, |
| | | title: 'å¢é建设活å¨', |
| | | status: 'completed', |
| | | startTime: '2024-01-14 16:00:00', |
| | | endTime: '2024-01-14 18:00:00', |
| | | location: 'å
¬å¸å¤§å
', |
| | | host: '人äºé¨', |
| | | participants: ['å
¨ä½åå·¥'], |
| | | agenda: [ |
| | | { time: '16:00-16:30', content: 'å¢é游æ', status: 'completed' }, |
| | | { time: '16:30-17:00', content: 'ç»éªå享', status: 'completed' }, |
| | | { time: '17:00-18:00', content: 'èªç±äº¤æµ', status: 'completed' } |
| | | ] |
| | | } |
| | | |
| | | ]) |
| | | |
| | | // å¯¹è¯æ¡ç¸å
³ |
| | |
| | | // è·åç¶æç±»å |
| | | const getStatusType = (status) => { |
| | | const statusMap = { |
| | | 'ongoing': 'success', |
| | | 'upcoming': 'warning', |
| | | 'completed': 'info' |
| | | '2': 'success', |
| | | '1': 'warning', |
| | | '0': 'info' |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | |
| | | // è·åç¶æææ¬ |
| | | const getStatusText = (status) => { |
| | | const statusMap = { |
| | | 'ongoing': 'è¿è¡ä¸', |
| | | 'upcoming': 'å³å°å¼å§', |
| | | 'completed': '已宿' |
| | | '2': 'è¿è¡ä¸', |
| | | '1': 'å³å°å¼å§', |
| | | '0': '已宿' |
| | | } |
| | | return statusMap[status] || 'æªç¥' |
| | | } |
| | |
| | | return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }) |
| | | } |
| | | |
| | | // å建ä¼è®® |
| | | const createMeeting = () => { |
| | | dialogVisible.value = true |
| | | // é置表å |
| | | Object.assign(meetingForm, { |
| | | title: '', |
| | | timeRange: [], |
| | | location: '', |
| | | host: '', |
| | | description: '' |
| | | |
| | | onMounted( async () => { |
| | | let [resp1,resp2] = await Promise.all([getMeetSummary(),getMeetSummaryItems()]) |
| | | stats.value = resp1.data |
| | | meetings.value = resp2.data.map(item => { |
| | | return { |
| | | ...item, |
| | | participants: JSON.parse(item.participants) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // æäº¤ä¼è®® |
| | | const submitMeeting = () => { |
| | | if (!meetingForm.title || !meetingForm.timeRange.length || !meetingForm.location || !meetingForm.host) { |
| | | ElMessage.warning('请填å宿´çä¼è®®ä¿¡æ¯') |
| | | return |
| | | } |
| | | |
| | | // å建æ°ä¼è®® |
| | | const newMeeting = { |
| | | id: Date.now(), |
| | | title: meetingForm.title, |
| | | status: 'upcoming', |
| | | startTime: meetingForm.timeRange[0], |
| | | endTime: meetingForm.timeRange[1], |
| | | location: meetingForm.location, |
| | | host: meetingForm.host, |
| | | participants: [meetingForm.host], |
| | | agenda: [ |
| | | { time: 'å¾
å®', content: 'è®®ç¨å¾
å®', status: 'pending' } |
| | | ] |
| | | } |
| | | |
| | | meetings.value.unshift(newMeeting) |
| | | stats.total++ |
| | | stats.upcoming++ |
| | | |
| | | ElMessage.success('ä¼è®®å建æå') |
| | | dialogVisible.value = false |
| | | } |
| | | |
| | | // å å
¥ä¼è®® |
| | | const joinMeeting = (meeting) => { |
| | | ElMessage.success(`å·²å å
¥ä¼è®®ï¼${meeting.title}`) |
| | | } |
| | | |
| | | // æ¥ç详æ
|
| | | const viewDetails = (meeting) => { |
| | | ElMessage.info(`æ¥çä¼è®®è¯¦æ
ï¼${meeting.title}`) |
| | | } |
| | | |
| | | // ç¼è¾ä¼è®® |
| | | const editMeeting = (meeting) => { |
| | | ElMessage.info(`ç¼è¾ä¼è®®ï¼${meeting.title}`) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | console.log('ä¼è®®çæ¿é¡µé¢å è½½å®æ') |
| | | }) |
| | | </script> |
| | |
| | | .stats-cards { |
| | | grid-template-columns: repeat(2, 1fr); |
| | | } |
| | | |
| | | |
| | | .meeting-header { |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | } |
| | | |
| | | |
| | | .meeting-info { |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | } |
| | | |
| | | |
| | | .meeting-actions { |
| | | flex-direction: column; |
| | | } |
| | | } |
| | | </style> |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="tabs-wrapper"> |
| | | <el-tabs |
| | | v-model="activeTab" |
| | | class="meeting-tabs" |
| | | @tab-change="handleTabChange" |
| | | > |
| | | <el-tab-pane label="ä¼è®®è®¾ç½®" name="setting" /> |
| | | <el-tab-pane label="ä¼è®®å表" name="index" /> |
| | | <el-tab-pane label="ä¼è®®ç³è¯·" name="application" /> |
| | | <el-tab-pane label="ä¼è®®å®¡æ¹" name="examine" /> |
| | | <el-tab-pane label="ä¼è®®åå¸" name="publish" /> |
| | | <el-tab-pane label="ä¼è®®æ»ç»" name="summary" /> |
| | | </el-tabs> |
| | | </div> |
| | | |
| | | <div class="tab-content"> |
| | | <keep-alive> |
| | | <component :is="currentComponent" /> |
| | | </keep-alive> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed } from 'vue' |
| | | import MeetSetting from '../notificationManagement/meetSetting/index.vue' |
| | | import MeetIndex from '../notificationManagement/meetIndex/index.vue' |
| | | import MeetApplication from '../notificationManagement/meetApplication/index.vue' |
| | | import MeetExamine from '../notificationManagement/meetExamine/index.vue' |
| | | import MeetPublish from '../notificationManagement/meetPublish/index.vue' |
| | | import MeetSummary from '../notificationManagement/summary/index.vue' |
| | | |
| | | const activeTab = ref('setting') |
| | | |
| | | const tabComponentMap = { |
| | | setting: MeetSetting, |
| | | index: MeetIndex, |
| | | application: MeetApplication, |
| | | examine: MeetExamine, |
| | | publish: MeetPublish, |
| | | summary: MeetSummary |
| | | } |
| | | |
| | | const currentComponent = computed(() => tabComponentMap[activeTab.value] || MeetSetting) |
| | | |
| | | function handleTabChange(name) { |
| | | activeTab.value = name |
| | | } |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | |
| | | .tabs-wrapper { |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .tab-content { |
| | | min-height: 400px; |
| | | } |
| | | </style> |
| | | |
| | |
| | | <!-- æç´¢è¡¨å --> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">å
¬åæ é¢ï¼</span> |
| | | <el-input |
| | | v-model="searchForm.noticeTitle" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥å
¬åæ é¢æç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | /> |
| | | <span class="search_title ml10">å
¬åç±»åï¼</span> |
| | | <el-select v-model="searchForm.noticeType" clearable @change="handleQuery" style="width: 240px"> |
| | | <el-option label="æ¾åéç¥" value="1" /> |
| | | <el-option label="设å¤ç»´ä¿®éç¥" value="2" /> |
| | | </el-select> |
| | | <span class="search_title ml10">ç¶æï¼</span> |
| | | <el-select v-model="searchForm.status" clearable @change="handleQuery" style="width: 240px"> |
| | | <el-option label="è稿" value="0" /> |
| | | <el-option label="å·²åå¸" value="1" /> |
| | | <el-option label="å·²ä¸çº¿" value="2" /> |
| | | </el-select> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px">æç´¢</el-button> |
| | | <el-button @click="resetQuery" style="margin-left: 10px">éç½®</el-button> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" @click="openForm('add')">æ°å¢å
Œ</el-button> |
| | | <el-button type="danger" plain @click="handleDelete" :disabled="!selectedIds.length">å é¤</el-button> |
| | | <el-button type="info" @click="openNoticeTypeDialog">å
¬åç±»åé
ç½®</el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- éç¥å
¬åæ¿ --> |
| | | <div class="notice-board"> |
| | | <!-- æ¾åéç¥åºå --> |
| | | <div class="notice-section" v-if="holidayNotices.length > 0"> |
| | | <div class="section-header"> |
| | | <h3>ð
æ¾åéç¥</h3> |
| | | <span class="section-count">{{ holidayNotices.length }}æ¡</span> |
| | | </div> |
| | | <div class="notice-cards"> |
| | | <div |
| | | v-for="notice in holidayNotices" |
| | | :key="notice.id" |
| | | class="notice-card holiday-card" |
| | | :class="{ 'urgent': notice.priority === '3' }" |
| | | > |
| | | <div class="card-header"> |
| | | <div class="card-title"> |
| | | <el-icon class="holiday-icon"><Calendar /></el-icon> |
| | | {{ notice.noticeTitle }} |
| | | </div> |
| | | <div class="card-actions"> |
| | | <el-button link type="primary" @click="handleEdit(notice)">ç¼è¾</el-button> |
| | | <el-button link type="danger" @click="handleDelete(notice.id)">å é¤</el-button> |
| | | <el-tabs v-model="activeNoticeTypeTab" @tab-change="handleNoticeTypeTabChange"> |
| | | <el-tab-pane |
| | | v-for="noticeType in noticeTypeList" |
| | | :key="noticeType.id" |
| | | :label="noticeType.noticeType" |
| | | :name="String(noticeType.id)" |
| | | > |
| | | <template #label> |
| | | <span>{{ noticeType.noticeType }} |
| | | <span class="tab-count" v-if="getNoticeCountByType(noticeType.id) > 0"> |
| | | ({{ getNoticeCountByType(noticeType.id) }}) |
| | | </span> |
| | | </span> |
| | | </template> |
| | | |
| | | <div class="notice-section"> |
| | | <div class="notice-cards"> |
| | | <div |
| | | v-for="notice in getNoticesByType(noticeType.id)" |
| | | :key="notice.id" |
| | | class="notice-card" |
| | | :class="{ 'urgent': notice.priority === '3' }" |
| | | > |
| | | <div class="card-header"> |
| | | <div class="card-title"> |
| | | <el-icon class="notice-icon"> |
| | | <Calendar/> |
| | | </el-icon> |
| | | {{ notice.title }} |
| | | </div> |
| | | <div class="card-actions"> |
| | | <el-button link type="primary" @click="handleEdit(notice)" :disabled="isNoticeExpired(notice)" v-if="notice.status !== 1">ç¼è¾</el-button> |
| | | <el-button link type="success" @click="handlePublish(notice)" v-if="notice.status === 0">åå¸</el-button> |
| | | <el-button link type="danger" @click="handleDelete(notice.id)" v-if="notice.status !== 1">å é¤</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="card-content"> |
| | | <p>{{ notice.content }}</p> |
| | | </div> |
| | | <div class="card-footer"> |
| | | <div class="card-meta"> |
| | | <span class="priority" :class="'priority-' + notice.priority"> |
| | | {{ getPriorityText(notice.priority) }} |
| | | </span> |
| | | <span class="status" :class="'status-' + getNoticeStatus(notice)"> |
| | | {{ getStatusText(getNoticeStatus(notice)) }} |
| | | </span> |
| | | </div> |
| | | <div class="card-info"> |
| | | <span class="creator">{{ notice.createUserName }}</span> |
| | | <span class="expiration" v-if="notice.expirationDate">æªæ¢æ¥æï¼{{ notice.expirationDate }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="card-remark" v-if="notice.remark"> |
| | | <el-icon> |
| | | <InfoFilled/> |
| | | </el-icon> |
| | | <span>{{ notice.remark }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="card-content"> |
| | | <p>{{ notice.noticeContent }}</p> |
| | | </div> |
| | | <div class="card-footer"> |
| | | <div class="card-meta"> |
| | | <span class="priority" :class="'priority-' + notice.priority"> |
| | | {{ getPriorityText(notice.priority) }} |
| | | </span> |
| | | <span class="status" :class="'status-' + notice.status"> |
| | | {{ getStatusText(notice.status) }} |
| | | </span> |
| | | </div> |
| | | <div class="card-info"> |
| | | <span class="creator">{{ notice.createBy }}</span> |
| | | <span class="time">{{ notice.createTime }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="card-remark" v-if="notice.remark"> |
| | | <el-icon><InfoFilled /></el-icon> |
| | | <span>{{ notice.remark }}</span> |
| | | |
| | | <pagination |
| | | v-if="getNoticePageByType(noticeType.id).total > 0" |
| | | :total="getNoticePageByType(noticeType.id).total" |
| | | :page="getNoticePageByType(noticeType.id).current" |
| | | :limit="getNoticePageByType(noticeType.id).size" |
| | | @pagination="(val) => handleNoticeCurrentChange(noticeType.id, val)" |
| | | /> |
| | | |
| | | <!-- ç©ºç¶æ --> |
| | | <div class="empty-state" v-if="getNoticesByType(noticeType.id).length === 0"> |
| | | <el-empty description="ææ éç¥å
Œ"/> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 设å¤ç»´ä¿®éç¥åºå --> |
| | | <div class="notice-section" v-if="maintenanceNotices.length > 0"> |
| | | <div class="section-header"> |
| | | <h3>ð§ 设å¤ç»´ä¿®éç¥</h3> |
| | | <span class="section-count">{{ maintenanceNotices.length }}æ¡</span> |
| | | </div> |
| | | <div class="notice-cards"> |
| | | <div |
| | | v-for="notice in maintenanceNotices" |
| | | :key="notice.id" |
| | | class="notice-card maintenance-card" |
| | | :class="{ 'urgent': notice.priority === '3' }" |
| | | > |
| | | <div class="card-header"> |
| | | <div class="card-title"> |
| | | <el-icon class="maintenance-icon"><Tools /></el-icon> |
| | | {{ notice.noticeTitle }} |
| | | </div> |
| | | <div class="card-actions"> |
| | | <el-button link type="primary" @click="handleEdit(notice)">ç¼è¾</el-button> |
| | | <el-button link type="danger" @click="handleDelete(notice.id)">å é¤</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="card-content"> |
| | | <p>{{ notice.noticeContent }}</p> |
| | | </div> |
| | | <div class="card-footer"> |
| | | <div class="card-meta"> |
| | | <span class="priority" :class="'priority-' + notice.priority"> |
| | | {{ getPriorityText(notice.priority) }} |
| | | </span> |
| | | <span class="status" :class="'status-' + notice.status"> |
| | | {{ getStatusText(notice.status) }} |
| | | </span> |
| | | </div> |
| | | <div class="card-info"> |
| | | <span class="creator">{{ notice.createBy }}</span> |
| | | <span class="time">{{ notice.createTime }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="card-remark" v-if="notice.remark"> |
| | | <el-icon><InfoFilled /></el-icon> |
| | | <span>{{ notice.remark }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- ç©ºç¶æ --> |
| | | <div class="empty-state" v-if="filteredNotices.length === 0"> |
| | | <el-empty description="ææ éç¥å
Œ" /> |
| | | </div> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | </div> |
| | | |
| | | <!-- æ°å¢/ç¼è¾å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | :title="dialogTitle" |
| | | v-model="dialogVisible" |
| | | width="800px" |
| | | append-to-body |
| | | @close="resetForm" |
| | | <el-dialog |
| | | :title="dialogTitle" |
| | | v-model="dialogVisible" |
| | | width="800px" |
| | | append-to-body |
| | | @close="resetForm" |
| | | > |
| | | <el-form ref="formRef" :model="form" :rules="rules" label-width="100px"> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å
¬åæ é¢" prop="noticeTitle"> |
| | | <el-input v-model="form.noticeTitle" 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="noticeType"> |
| | | <el-select v-model="form.noticeType" placeholder="è¯·éæ©å
¬åç±»å" style="width: 100%"> |
| | | <el-option label="æ¾åéç¥" value="1" /> |
| | | <el-option label="设å¤ç»´ä¿®éç¥" value="2" /> |
| | | <el-form-item label="å
¬åç±»å" prop="type"> |
| | | <el-select v-model="form.type" placeholder="è¯·éæ©å
¬åç±»å" style="width: 100%"> |
| | | <el-option |
| | | v-for="item in noticeTypeList" |
| | | :key="item.id" |
| | | :label="item.noticeType" |
| | | :value="item.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¶æ"> |
| | | <el-radio-group v-model="form.status"> |
| | | <el-radio value="0">è稿</el-radio> |
| | | <el-radio value="1">å·²åå¸</el-radio> |
| | | <el-radio value="2">å·²ä¸çº¿</el-radio> |
| | | <el-radio :value="0">è稿</el-radio> |
| | | <el-radio :value="1">æ£å¼åå¸</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¼å
级"> |
| | | <el-select v-model="form.priority" placeholder="è¯·éæ©ä¼å
级" style="width: 100%"> |
| | | <el-option label="æ®é" value="1" /> |
| | | <el-option label="éè¦" value="2" /> |
| | | <el-option label="ç´§æ¥" value="3" /> |
| | | <el-option label="æ®é" :value="1"/> |
| | | <el-option label="éè¦" :value="2"/> |
| | | <el-option label="ç´§æ¥" :value="3"/> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è¿ææ¶é´" prop="expirationDate"> |
| | | <el-date-picker v-model="form.expirationDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="date" |
| | | placeholder="è¯·éæ©" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="å
¬åå
容" prop="noticeContent"> |
| | | <el-form-item label="å
¬åå
容" prop="content"> |
| | | <el-input |
| | | v-model="form.noticeContent" |
| | | type="textarea" |
| | | :rows="6" |
| | | placeholder="请è¾å
¥å
¬åå
容" |
| | | maxlength="500" |
| | | show-word-limit |
| | | v-model="form.content" |
| | | type="textarea" |
| | | :rows="6" |
| | | placeholder="请è¾å
¥å
¬åå
容" |
| | | maxlength="500" |
| | | show-word-limit |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | <el-col :span="24"> |
| | | <el-form-item label="夿³¨"> |
| | | <el-input |
| | | v-model="form.remark" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯" |
| | | maxlength="200" |
| | | show-word-limit |
| | | v-model="form.remark" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯" |
| | | maxlength="200" |
| | | show-word-limit |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- å
¬åç±»åé
ç½®å¼¹æ¡ --> |
| | | <el-dialog |
| | | v-model="noticeTypeDialogVisible" |
| | | title="å
¬åç±»åé
ç½®" |
| | | width="800px" |
| | | @close="handleNoticeTypeDialogClose" |
| | | > |
| | | <div class="notice-type-container"> |
| | | <div class="notice-type-header"> |
| | | <el-button type="primary" @click="handleAddNoticeType">æ°å¢ç±»å</el-button> |
| | | </div> |
| | | <el-table :data="noticeTypeList" border style="width: 100%"> |
| | | <el-table-column prop="id" label="ID" width="80" align="center"/> |
| | | <el-table-column prop="noticeType" label="å
¬åç±»å" align="center"> |
| | | <template #default="scope"> |
| | | <el-input |
| | | v-if="scope.row.editing" |
| | | v-model="scope.row.noticeType" |
| | | placeholder="请è¾å
¥å
¬åç±»å" |
| | | /> |
| | | <span v-else>{{ scope.row.noticeType }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="200" align="center"> |
| | | <template #default="scope"> |
| | | <el-button |
| | | v-if="scope.row.editing" |
| | | link |
| | | type="primary" |
| | | size="small" |
| | | @click="handleSaveNoticeType(scope.row)" |
| | | > |
| | | ä¿å |
| | | </el-button> |
| | | <el-button |
| | | v-if="scope.row.editing" |
| | | link |
| | | type="info" |
| | | size="small" |
| | | @click="handleCancelEdit(scope.row)" |
| | | > |
| | | åæ¶ |
| | | </el-button> |
| | | <el-button |
| | | v-if="!scope.row.editing" |
| | | link |
| | | type="primary" |
| | | size="small" |
| | | @click="handleEditNoticeType(scope.row)" |
| | | > |
| | | ç¼è¾ |
| | | </el-button> |
| | | <el-button |
| | | v-if="!scope.row.editing" |
| | | link |
| | | type="danger" |
| | | size="small" |
| | | @click="handleDeleteNoticeType(scope.row)" |
| | | > |
| | | å é¤ |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Search, Calendar, Tools, InfoFilled } from "@element-plus/icons-vue"; |
| | | import { onMounted, ref, reactive, toRefs, computed } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import {Calendar, InfoFilled} from "@element-plus/icons-vue"; |
| | | import {onMounted, ref, reactive, toRefs, computed} from "vue"; |
| | | import {ElMessage, ElMessageBox} from "element-plus"; |
| | | import {useRoute} from "vue-router"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { |
| | | addNotice, |
| | | delNotice, |
| | | getCount, |
| | | listNotice, |
| | | updateNotice, |
| | | listNoticeType, |
| | | addNoticeType, |
| | | delNoticeType |
| | | } from "../../../api/collaborativeApproval/noticeManagement.js"; |
| | | import pagination from "../../../components/PIMTable/Pagination.vue"; |
| | | |
| | | const userStore = useUserStore(); |
| | | const route = useRoute(); |
| | | |
| | | // ååºå¼æ°æ® |
| | | const data = reactive({ |
| | | searchForm: { |
| | | noticeTitle: "", |
| | | noticeType: "", |
| | | status: "", |
| | | title: "", |
| | | type: undefined, |
| | | status: undefined, |
| | | }, |
| | | form: { |
| | | id: undefined, |
| | | noticeTitle: "", |
| | | noticeType: "", |
| | | noticeContent: "", |
| | | status: "0", |
| | | priority: "1", |
| | | title: "", |
| | | type: null, |
| | | content: "", |
| | | status: 0, |
| | | priority: 1, |
| | | remark: "", |
| | | createBy: "", |
| | | createTime: "", |
| | | expirationDate: "", |
| | | }, |
| | | rules: { |
| | | noticeTitle: [ |
| | | { required: true, message: "å
¬åæ é¢ä¸è½ä¸ºç©º", trigger: "blur" } |
| | | title: [ |
| | | {required: true, message: "å
¬åæ é¢ä¸è½ä¸ºç©º", trigger: "blur"} |
| | | ], |
| | | noticeType: [ |
| | | { required: true, message: "è¯·éæ©å
¬åç±»å", trigger: "change" } |
| | | type: [ |
| | | {required: true, message: "è¯·éæ©å
¬åç±»å", trigger: "change"} |
| | | ], |
| | | noticeContent: [ |
| | | { required: true, message: "å
¬åå
容ä¸è½ä¸ºç©º", trigger: "blur" } |
| | | content: [ |
| | | {required: true, message: "å
¬åå
容ä¸è½ä¸ºç©º", trigger: "blur"} |
| | | ], |
| | | expirationDate: [ |
| | | {required: true, message: "è¯·éæ©æ¥æ", trigger: "change"} |
| | | ] |
| | | } |
| | | }); |
| | | |
| | | const { searchForm, form, rules } = toRefs(data); |
| | | const {searchForm, form, rules} = toRefs(data); |
| | | |
| | | // 页é¢ç¶æ |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref(""); |
| | | const selectedIds = ref([]); |
| | | const formRef = ref(); |
| | | |
| | | // æ¨¡ææ°æ® - æ ¹æ®æ³å®èåæ¥è®¾è®¡ |
| | | const mockData = [ |
| | | { |
| | | id: 1, |
| | | noticeTitle: "2024å¹´æ¥èæ¾åéç¥", |
| | | noticeType: "1", |
| | | priority: "2", |
| | | status: "1", |
| | | noticeContent: "æ ¹æ®å½å¡é¢åå
Œ
éç¥ï¼2024å¹´æ¥èæ¾å宿å¦ä¸ï¼2æ10æ¥ï¼åä¸ï¼è³2æ17æ¥ï¼åå
«ï¼æ¾åè°ä¼ï¼å
±8天ã2æ4æ¥ï¼æææ¥ï¼ã2æ18æ¥ï¼æææ¥ï¼ä¸çã请åé¨é¨æåå好工ä½å®æã", |
| | | remark: "æ¾åæé´è¯·ä¿æææºç
éï¼å¦æç´§æ¥äºå¡åæ¶èç³»", |
| | | createBy: "人äºé¨", |
| | | createTime: "2024-01-15 10:30:00" |
| | | }, |
| | | { |
| | | id: 2, |
| | | noticeTitle: "2024å¹´æ¸
æèæ¾åéç¥", |
| | | noticeType: "1", |
| | | priority: "1", |
| | | status: "1", |
| | | noticeContent: "æ ¹æ®å½å¡é¢åå
Œ
éç¥ï¼2024å¹´æ¸
æèæ¾å宿å¦ä¸ï¼4æ4æ¥ï¼ææåï¼è³4æ6æ¥ï¼ææå
ï¼æ¾åè°ä¼ï¼å
±3天ã4æ7æ¥ï¼æææ¥ï¼ä¸çã", |
| | | remark: "请åé¨é¨å好å¼ç宿ï¼ç¡®ä¿èæ¥æé´å项工使£å¸¸è¿è½¬", |
| | | createBy: "è¡æ¿é¨", |
| | | createTime: "2024-01-14 14:20:00" |
| | | }, |
| | | { |
| | | id: 3, |
| | | noticeTitle: "2024å¹´å³å¨èæ¾åéç¥", |
| | | noticeType: "1", |
| | | priority: "1", |
| | | status: "1", |
| | | noticeContent: "æ ¹æ®å½å¡é¢åå
Œ
éç¥ï¼2024å¹´å³å¨èæ¾å宿å¦ä¸ï¼5æ1æ¥ï¼ææä¸ï¼è³5æ5æ¥ï¼æææ¥ï¼æ¾åè°ä¼ï¼å
±5天ã4æ28æ¥ï¼æææ¥ï¼ã5æ11æ¥ï¼ææå
ï¼ä¸çã", |
| | | remark: "æ¾åå请å
³éçµæºï¼é好é¨çªï¼æ³¨æå®å
¨", |
| | | createBy: "è¡æ¿é¨", |
| | | createTime: "2024-01-13 09:15:00" |
| | | }, |
| | | { |
| | | id: 4, |
| | | noticeTitle: "2024年端åèæ¾åéç¥", |
| | | noticeType: "1", |
| | | priority: "1", |
| | | status: "1", |
| | | noticeContent: "æ ¹æ®å½å¡é¢åå
Œ
éç¥ï¼2024年端åèæ¾å宿å¦ä¸ï¼6æ8æ¥ï¼ææå
ï¼è³6æ10æ¥ï¼ææä¸ï¼æ¾åè°ä¼ï¼å
±3天ã6æ11æ¥ï¼ææäºï¼ä¸çã", |
| | | remark: "ç¥å¤§å®¶ç«¯åèå¿«ä¹ï¼é家幸ç¦ï¼", |
| | | createBy: "è¡æ¿é¨", |
| | | createTime: "2024-01-12 16:30:00" |
| | | }, |
| | | { |
| | | id: 5, |
| | | noticeTitle: "2024å¹´ä¸ç§èæ¾åéç¥", |
| | | noticeType: "1", |
| | | priority: "1", |
| | | status: "1", |
| | | noticeContent: "æ ¹æ®å½å¡é¢åå
Œ
éç¥ï¼2024å¹´ä¸ç§èæ¾å宿å¦ä¸ï¼9æ15æ¥ï¼æææ¥ï¼è³9æ17æ¥ï¼ææäºï¼æ¾åè°ä¼ï¼å
±3天ã9æ14æ¥ï¼ææå
ï¼ä¸çã", |
| | | remark: "ä¸ç§ä½³èï¼ç¥å¤§å®¶å¢åç¾æ»¡ï¼å¹¸ç¦å®åº·ï¼", |
| | | createBy: "è¡æ¿é¨", |
| | | createTime: "2024-01-11 11:20:00" |
| | | }, |
| | | { |
| | | id: 6, |
| | | noticeTitle: "2024å¹´å½åºèæ¾åéç¥", |
| | | noticeType: "1", |
| | | priority: "2", |
| | | status: "1", |
| | | noticeContent: "æ ¹æ®å½å¡é¢åå
Œ
éç¥ï¼2024å¹´å½åºèæ¾å宿å¦ä¸ï¼10æ1æ¥ï¼ææäºï¼è³10æ7æ¥ï¼ææä¸ï¼æ¾åè°ä¼ï¼å
±7天ã9æ29æ¥ï¼æææ¥ï¼ã10æ12æ¥ï¼ææå
ï¼ä¸çã", |
| | | remark: "å½åºæé´è¯·åé¨é¨å好å¼ç宿ï¼ç¡®ä¿å®å
¨ç¨³å®", |
| | | createBy: "è¡æ¿é¨", |
| | | createTime: "2024-01-10 15:45:00" |
| | | }, |
| | | { |
| | | id: 7, |
| | | noticeTitle: "A车é´ç产线年度æ£ä¿®éç¥", |
| | | noticeType: "2", |
| | | priority: "2", |
| | | status: "1", |
| | | noticeContent: "A车é´ç产线å°äº2024å¹´1æ20æ¥ï¼å¨å
ï¼è¿è¡å¹´åº¦æ£ä¿®ç»´æ¤ï¼é¢è®¡åå·¥8å°æ¶ãæ£ä¿®å
容å
æ¬ï¼è®¾å¤æ¸
æ´ã润æ»ä¿å
»ãå®å
¨è£
ç½®æ£æ¥çã请ç产é¨é¨æåè°æ´ç产计åã", |
| | | remark: "ç»´ä¿®æé´è¯·ç¸å
³äººåé
åï¼ç¡®ä¿æ£ä¿®å·¥ä½å®å
¨é¡ºå©è¿è¡", |
| | | createBy: "设å¤é¨", |
| | | createTime: "2024-01-14 14:20:00" |
| | | }, |
| | | { |
| | | id: 8, |
| | | noticeTitle: "B车é´è®¾å¤é¢é²æ§ç»´æ¤éç¥", |
| | | noticeType: "2", |
| | | priority: "1", |
| | | status: "1", |
| | | noticeContent: "B车é´å
³é®è®¾å¤å°äº2024å¹´1æ25æ¥è¿è¡é¢é²æ§ç»´æ¤ï¼é¢è®¡åå·¥4å°æ¶ãç»´æ¤å
容å
æ¬ï¼è®¾å¤æ£æ¥ãé¶ä»¶æ´æ¢ãæ§è½æµè¯çã请ç¸å
³é¨é¨é
åã", |
| | | remark: "ç»´æ¤å®æåå°è¿è¡è¯è¿è¡ï¼ç¡®ä¿è®¾å¤æ£å¸¸è¿è¡", |
| | | createBy: "设å¤é¨", |
| | | createTime: "2024-01-13 09:15:00" |
| | | } |
| | | ]; |
| | | // å
¬åç±»åé
ç½®ç¸å
³ |
| | | const noticeTypeDialogVisible = ref(false); |
| | | const noticeTypeList = ref([]); |
| | | const activeNoticeTypeTab = ref(''); |
| | | |
| | | // éç¥æ°æ® - ä½¿ç¨ Map åå¨ï¼key 为类å id |
| | | const noticesMap = ref({}); |
| | | const noticePagesMap = ref({}); |
| | | |
| | | |
| | | // 计ç®å±æ§ |
| | | const filteredNotices = computed(() => { |
| | | let filtered = [...mockData]; |
| | | |
| | | |
| | | if (searchForm.value.noticeTitle) { |
| | | filtered = filtered.filter(item => |
| | | item.noticeTitle.includes(searchForm.value.noticeTitle) |
| | | filtered = filtered.filter(item => |
| | | item.noticeTitle.includes(searchForm.value.noticeTitle) |
| | | ); |
| | | } |
| | | if (searchForm.value.noticeType) { |
| | | filtered = filtered.filter(item => |
| | | item.noticeType === searchForm.value.noticeType |
| | | filtered = filtered.filter(item => |
| | | item.noticeType === searchForm.value.noticeType |
| | | ); |
| | | } |
| | | if (searchForm.value.status !== "") { |
| | | filtered = filtered.filter(item => |
| | | item.status === searchForm.value.status |
| | | filtered = filtered.filter(item => |
| | | item.status === searchForm.value.status |
| | | ); |
| | | } |
| | | |
| | | |
| | | return filtered; |
| | | }); |
| | | |
| | | const holidayNotices = computed(() => { |
| | | return filteredNotices.value.filter(notice => notice.noticeType === "1"); |
| | | }); |
| | | |
| | | const maintenanceNotices = computed(() => { |
| | | return filteredNotices.value.filter(notice => notice.noticeType === "2"); |
| | | }); |
| | | |
| | | // æ¹æ³å®ä¹ |
| | |
| | | |
| | | const resetQuery = () => { |
| | | searchForm.value = { |
| | | noticeTitle: "", |
| | | noticeType: "", |
| | | title: "", |
| | | type: "", |
| | | status: "" |
| | | }; |
| | | }; |
| | | |
| | | const getPriorityText = (priority) => { |
| | | const priorityMap = { "1": "æ®é", "2": "éè¦", "3": "ç´§æ¥" }; |
| | | const priorityMap = {"1": "æ®é", "2": "éè¦", "3": "ç´§æ¥"}; |
| | | return priorityMap[priority] || "æ®é"; |
| | | }; |
| | | |
| | | const getStatusText = (status) => { |
| | | const statusMap = { "0": "è稿", "1": "å·²åå¸", "2": "å·²ä¸çº¿" }; |
| | | const statusMap = {"0": "è稿", "1": "å·²åå¸", "2": "å·²è¿æ"}; |
| | | return statusMap[status] || "æªç¥"; |
| | | }; |
| | | |
| | | const isNoticeExpired = (notice) => { |
| | | if (!notice || !notice.expirationDate) { |
| | | return false; |
| | | } |
| | | |
| | | const expiration = new Date(notice.expirationDate); |
| | | |
| | | if (Number.isNaN(expiration.getTime())) { |
| | | return false; |
| | | } |
| | | |
| | | expiration.setHours(23, 59, 59, 999); |
| | | |
| | | return new Date() > expiration; |
| | | }; |
| | | |
| | | const getNoticeStatus = (notice) => { |
| | | const normalizedStatus = notice && notice.status !== undefined && notice.status !== null |
| | | ? String(notice.status) |
| | | : "0"; |
| | | |
| | | return isNoticeExpired(notice) ? "2" : normalizedStatus; |
| | | }; |
| | | |
| | | const openForm = (type) => { |
| | |
| | | dialogTitle.value = "æ°å¢å
Œ"; |
| | | form.value = { |
| | | id: undefined, |
| | | noticeTitle: "", |
| | | noticeType: "", |
| | | noticeContent: "", |
| | | status: "0", |
| | | priority: "1", |
| | | title: "", |
| | | type: undefined, |
| | | content: "", |
| | | status: 0, |
| | | priority: 1, |
| | | remark: "", |
| | | createBy: userStore.name || "å½åç¨æ·", |
| | | createTime: new Date().toLocaleString() |
| | | expirationDate: "", |
| | | }; |
| | | } |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const handleEdit = (row) => { |
| | | if (isNoticeExpired(row)) { |
| | | ElMessage.warning("å·²è¿æçå
¬åä¸å¯ç¼è¾"); |
| | | return; |
| | | } |
| | | dialogTitle.value = "ç¼è¾å
Œ"; |
| | | form.value = { ...row }; |
| | | form.value = {...row}; |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const handleSelectionChange = (selection) => { |
| | | selectedIds.value = selection.map(item => item.id); |
| | | }; |
| | | |
| | | const handleDelete = (id) => { |
| | | ElMessageBox.confirm( |
| | | "确认å é¤è¿æ¡å
¬ååï¼", |
| | | "æç¤º", |
| | | { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning" |
| | | } |
| | | "确认å é¤è¿æ¡å
¬ååï¼", |
| | | "æç¤º", |
| | | { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning" |
| | | } |
| | | ).then(() => { |
| | | const index = mockData.findIndex(item => item.id === id); |
| | | if (index > -1) { |
| | | mockData.splice(index, 1); |
| | | delNotice(id).then(res => { |
| | | ElMessage.success("å 餿å"); |
| | | } |
| | | resetTable() |
| | | }) |
| | | }); |
| | | }; |
| | | |
| | | const handlePublish = (notice) => { |
| | | ElMessageBox.confirm( |
| | | "确认åå¸è¿æ¡å
¬ååï¼", |
| | | "æç¤º", |
| | | { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "info" |
| | | } |
| | | ).then(() => { |
| | | updateNotice({ |
| | | ...notice, |
| | | status: 1 |
| | | }).then(res => { |
| | | if (res.code === 200) { |
| | | ElMessage.success("å叿å"); |
| | | resetTable() |
| | | } |
| | | }) |
| | | }); |
| | | }; |
| | | |
| | |
| | | if (valid) { |
| | | if (form.value.id) { |
| | | // ç¼è¾æ¨¡å¼ |
| | | const index = mockData.findIndex(item => item.id === form.value.id); |
| | | if (index > -1) { |
| | | mockData[index] = { ...form.value }; |
| | | } |
| | | ElMessage.success("ä¿®æ¹æå"); |
| | | updateNotice(form.value).then(res => { |
| | | ElMessage.success("ä¿®æ¹æå"); |
| | | resetTable() |
| | | }) |
| | | } else { |
| | | // æ°å¢æ¨¡å¼ |
| | | const newId = Math.max(...mockData.map(item => item.id)) + 1; |
| | | const newNotice = { |
| | | ...form.value, |
| | | id: newId, |
| | | createTime: new Date().toLocaleString() |
| | | }; |
| | | mockData.unshift(newNotice); |
| | | ElMessage.success("æ°å¢æå"); |
| | | addNotice(form.value).then(res => { |
| | | ElMessage.success("æ°å¢æå"); |
| | | resetTable() |
| | | }) |
| | | } |
| | | dialogVisible.value = false; |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // åå§åæä¸ªç±»åçåé¡µæ°æ® |
| | | const initNoticePage = (typeId) => { |
| | | if (!noticePagesMap.value[typeId]) { |
| | | noticePagesMap.value[typeId] = { |
| | | total: 0, |
| | | current: 1, |
| | | size: 10 |
| | | }; |
| | | } |
| | | if (!noticesMap.value[typeId]) { |
| | | noticesMap.value[typeId] = []; |
| | | } |
| | | }; |
| | | |
| | | // è·åæä¸ªç±»åçéç¥å表 |
| | | const getNoticesByType = (typeId) => { |
| | | return noticesMap.value[typeId] || []; |
| | | }; |
| | | |
| | | // è·åæä¸ªç±»åçåé¡µæ°æ® |
| | | const getNoticePageByType = (typeId) => { |
| | | return noticePagesMap.value[typeId] || { total: 0, current: 1, size: 10 }; |
| | | }; |
| | | |
| | | // è·åæä¸ªç±»åçæ°é |
| | | const getNoticeCountByType = (typeId) => { |
| | | return getNoticePageByType(typeId).total || 0; |
| | | }; |
| | | |
| | | // è·åæä¸ªç±»åçéç¥æ°æ® |
| | | const fetchNoticesByType = (typeId) => { |
| | | initNoticePage(typeId); |
| | | const pageData = noticePagesMap.value[typeId]; |
| | | listNotice({...pageData, type: typeId}).then(res => { |
| | | if (res.code === 200) { |
| | | noticesMap.value[typeId] = res.data.records || []; |
| | | noticePagesMap.value[typeId].total = res.data.total || 0; |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // å¤çå页åå |
| | | const handleNoticeCurrentChange = (typeId, val) => { |
| | | initNoticePage(typeId); |
| | | noticePagesMap.value[typeId].size = val.limit; |
| | | noticePagesMap.value[typeId].current = val.page; |
| | | fetchNoticesByType(typeId); |
| | | }; |
| | | |
| | | // å¤ç tab 忢 |
| | | const handleNoticeTypeTabChange = (tabName) => { |
| | | activeNoticeTypeTab.value = tabName; |
| | | const typeId = Number(tabName); |
| | | fetchNoticesByType(typeId); |
| | | }; |
| | | |
| | | const resetTable = () => { |
| | | // éç½®ææç±»åçå页并鿰è·åæ°æ® |
| | | noticeTypeList.value.forEach(type => { |
| | | initNoticePage(type.id); |
| | | noticePagesMap.value[type.id].current = 1; |
| | | noticePagesMap.value[type.id].size = 10; |
| | | fetchNoticesByType(type.id); |
| | | }); |
| | | }; |
| | | |
| | |
| | | formRef.value?.resetFields(); |
| | | }; |
| | | |
| | | // å
¬åç±»åé
ç½®ç¸å
³æ¹æ³ |
| | | const openNoticeTypeDialog = () => { |
| | | noticeTypeDialogVisible.value = true; |
| | | fetchNoticeTypeList(); |
| | | }; |
| | | |
| | | const fetchNoticeTypeList = () => { |
| | | return listNoticeType().then(res => { |
| | | if (res.code === 200) { |
| | | noticeTypeList.value = res.data.map(item => ({ |
| | | ...item, |
| | | editing: false |
| | | })); |
| | | |
| | | // æ£æ¥è·¯ç±åæ°ä¸ç type |
| | | const routeType = route.query.type; |
| | | let targetTypeId = null; |
| | | |
| | | if (routeType) { |
| | | // å¦æè·¯ç±åæ°ä¸æ typeï¼æ¥æ¾å¯¹åºçç±»å |
| | | const typeId = Number(routeType); |
| | | const foundType = noticeTypeList.value.find(item => item.id === typeId); |
| | | if (foundType) { |
| | | targetTypeId = typeId; |
| | | } |
| | | } |
| | | |
| | | // 妿æç±»åæ°æ® |
| | | if (noticeTypeList.value.length > 0) { |
| | | // å¦æè·¯ç±åæ°æå®äºç±»åä¸åå¨ï¼ä½¿ç¨è·¯ç±åæ°çç±»å |
| | | // å¦åå¦ææ²¡æéä¸ tabï¼é»è®¤éä¸ç¬¬ä¸ä¸ª |
| | | if (targetTypeId !== null) { |
| | | activeNoticeTypeTab.value = String(targetTypeId); |
| | | fetchNoticesByType(targetTypeId); |
| | | } else if (!activeNoticeTypeTab.value) { |
| | | activeNoticeTypeTab.value = String(noticeTypeList.value[0].id); |
| | | fetchNoticesByType(noticeTypeList.value[0].id); |
| | | } |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const handleAddNoticeType = () => { |
| | | const newItem = { |
| | | id: undefined, |
| | | noticeType: '', |
| | | editing: true |
| | | }; |
| | | noticeTypeList.value.push(newItem); |
| | | }; |
| | | |
| | | const handleEditNoticeType = (row) => { |
| | | // ä¿ååå§å¼ |
| | | row.originalNoticeType = row.noticeType; |
| | | row.editing = true; |
| | | }; |
| | | |
| | | const handleSaveNoticeType = (row) => { |
| | | if (!row.noticeType || row.noticeType.trim() === '') { |
| | | ElMessage.warning('å
¬åç±»åä¸è½ä¸ºç©º'); |
| | | return; |
| | | } |
| | | |
| | | const data = { |
| | | noticeType: row.noticeType.trim() |
| | | }; |
| | | |
| | | if (row.id) { |
| | | // ç¼è¾æ¨¡å¼ - å
å é¤åæ·»å ï¼å ä¸ºåªæ add å del æ¥å£ï¼ |
| | | delNoticeType(row.id).then(res => { |
| | | if (res.code === 200) { |
| | | addNoticeType(data).then(addRes => { |
| | | if (addRes.code === 200) { |
| | | ElMessage.success('ç¼è¾æå'); |
| | | row.editing = false; |
| | | delete row.originalNoticeType; |
| | | fetchNoticeTypeList().then(() => { |
| | | // 妿å½åéä¸çç±»å被ç¼è¾ï¼éè¦éæ°è·åæ°æ® |
| | | if (activeNoticeTypeTab.value === String(row.id)) { |
| | | fetchNoticesByType(addRes.data?.id || row.id); |
| | | } |
| | | }); |
| | | } |
| | | }); |
| | | } |
| | | }); |
| | | } else { |
| | | // æ°å¢æ¨¡å¼ |
| | | addNoticeType(data).then(res => { |
| | | if (res.code === 200) { |
| | | ElMessage.success('æ°å¢æå'); |
| | | row.editing = false; |
| | | fetchNoticeTypeList(); |
| | | } |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | const handleDeleteNoticeType = (row) => { |
| | | // å¦ææ²¡æidï¼è¯´ææ¯æ°å¢ä½æªä¿åçè¡ï¼ç´æ¥ä»å端å é¤ |
| | | if (!row.id) { |
| | | const index = noticeTypeList.value.indexOf(row); |
| | | if (index > -1) { |
| | | noticeTypeList.value.splice(index, 1); |
| | | } |
| | | return; |
| | | } |
| | | |
| | | // 妿æidï¼è°ç¨å端æ¥å£å é¤ |
| | | ElMessageBox.confirm( |
| | | "确认å é¤è¿ä¸ªå
¬åç±»ååï¼", |
| | | "æç¤º", |
| | | { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning" |
| | | } |
| | | ).then(() => { |
| | | delNoticeType(row.id).then(res => { |
| | | if (res.code === 200) { |
| | | ElMessage.success("å 餿å"); |
| | | // 妿å é¤çæ¯å½åéä¸çç±»åï¼åæ¢å°ç¬¬ä¸ä¸ªç±»å |
| | | if (activeNoticeTypeTab.value === String(row.id)) { |
| | | fetchNoticeTypeList().then(() => { |
| | | if (noticeTypeList.value.length > 0) { |
| | | activeNoticeTypeTab.value = String(noticeTypeList.value[0].id); |
| | | fetchNoticesByType(noticeTypeList.value[0].id); |
| | | } else { |
| | | activeNoticeTypeTab.value = ''; |
| | | } |
| | | }); |
| | | } else { |
| | | fetchNoticeTypeList(); |
| | | } |
| | | } |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const handleCancelEdit = (row) => { |
| | | if (!row.id) { |
| | | // å¦ææ¯æ°å¢ä½æªä¿åçè¡ï¼ç§»é¤å® |
| | | const index = noticeTypeList.value.indexOf(row); |
| | | if (index > -1) { |
| | | noticeTypeList.value.splice(index, 1); |
| | | } |
| | | } else { |
| | | // 妿æ¯ç¼è¾ä¸çè¡ï¼åæ¶ç¼è¾ç¶æå¹¶æ¢å¤åå¼ |
| | | row.editing = false; |
| | | if (row.originalNoticeType !== undefined) { |
| | | row.noticeType = row.originalNoticeType; |
| | | delete row.originalNoticeType; |
| | | } |
| | | } |
| | | }; |
| | | |
| | | const handleNoticeTypeDialogClose = () => { |
| | | // å
³éå¼¹æ¡æ¶ï¼åæ¶ææç¼è¾ç¶æ |
| | | noticeTypeList.value.forEach(item => { |
| | | if (item.editing && !item.id) { |
| | | // å¦ææ¯æ°å¢ä½æªä¿åçè¡ï¼ç§»é¤å® |
| | | const index = noticeTypeList.value.indexOf(item); |
| | | if (index > -1) { |
| | | noticeTypeList.value.splice(index, 1); |
| | | } |
| | | } else if (item.editing) { |
| | | // 妿æ¯ç¼è¾ä¸çè¡ï¼åæ¶ç¼è¾ç¶æå¹¶æ¢å¤åå¼ |
| | | item.editing = false; |
| | | if (item.originalNoticeType !== undefined) { |
| | | item.noticeType = item.originalNoticeType; |
| | | delete item.originalNoticeType; |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // çå½å¨æ |
| | | onMounted(() => { |
| | | // 页é¢å è½½å®æ |
| | | // å
è·åå
¬åç±»åå表ï¼ç¶åæ ¹æ®ç±»åè·åéç¥æ°æ® |
| | | fetchNoticeTypeList(); |
| | | }); |
| | | </script> |
| | | |
| | |
| | | box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); |
| | | } |
| | | |
| | | .holiday-card { |
| | | border-left-color: #67c23a; |
| | | .notice-icon { |
| | | color: #409eff; |
| | | margin-right: 8px; |
| | | font-size: 18px; |
| | | } |
| | | |
| | | .maintenance-card { |
| | | border-left-color: #e6a23c; |
| | | .tab-count { |
| | | color: #909399; |
| | | font-size: 12px; |
| | | margin-left: 4px; |
| | | } |
| | | |
| | | .urgent { |
| | |
| | | flex: 1; |
| | | } |
| | | |
| | | .holiday-icon { |
| | | color: #67c23a; |
| | | margin-right: 8px; |
| | | font-size: 18px; |
| | | } |
| | | |
| | | .maintenance-icon { |
| | | color: #e6a23c; |
| | | margin-right: 8px; |
| | | font-size: 18px; |
| | | } |
| | | |
| | | .card-actions { |
| | | display: flex; |
| | |
| | | color: #606266; |
| | | line-height: 1.6; |
| | | font-size: 14px; |
| | | word-break: break-all; |
| | | white-space: pre-wrap; |
| | | overflow-wrap: break-word; |
| | | } |
| | | |
| | | .card-footer { |
| | |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .priority-1 { background: #f0f9ff; color: #0369a1; } |
| | | .priority-2 { background: #fef3c7; color: #d97706; } |
| | | .priority-3 { background: #fef2f2; color: #dc2626; } |
| | | .priority-1 { |
| | | background: #f0f9ff; |
| | | color: #0369a1; |
| | | } |
| | | |
| | | .status-0 { background: #f3f4f6; color: #6b7280; } |
| | | .status-1 { background: #d1fae5; color: #059669; } |
| | | .status-2 { background: #fef3c7; color: #d97706; } |
| | | .priority-2 { |
| | | background: #fef3c7; |
| | | color: #d97706; |
| | | } |
| | | |
| | | .priority-3 { |
| | | background: #fef2f2; |
| | | color: #dc2626; |
| | | } |
| | | |
| | | .status-0 { |
| | | background: #f3f4f6; |
| | | color: #6b7280; |
| | | } |
| | | |
| | | .status-1 { |
| | | background: #d1fae5; |
| | | color: #059669; |
| | | } |
| | | |
| | | .status-2 { |
| | | background: #fef3c7; |
| | | color: #d97706; |
| | | } |
| | | |
| | | .card-info { |
| | | display: flex; |
| | |
| | | .creator { |
| | | font-weight: 500; |
| | | margin-bottom: 2px; |
| | | } |
| | | |
| | | .expiration { |
| | | margin-top: 2px; |
| | | } |
| | | |
| | | .card-remark { |
| | |
| | | text-align: right; |
| | | } |
| | | |
| | | .notice-type-container { |
| | | padding: 10px 0; |
| | | } |
| | | |
| | | .notice-type-header { |
| | | margin-bottom: 15px; |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | } |
| | | |
| | | /* ååºå¼è®¾è®¡ */ |
| | | @media (max-width: 768px) { |
| | | .notice-cards { |
| | | grid-template-columns: 1fr; |
| | | } |
| | | |
| | | |
| | | .search_form { |
| | | flex-direction: column; |
| | | gap: 15px; |
| | | } |
| | | |
| | | |
| | | .search_form > div { |
| | | width: 100%; |
| | | } |
| | |
| | | v-model="form.expireDate" |
| | | type="date" |
| | | placeholder="è¯·éæ©æææ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | |
| | | <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%" |
| | | /> |
| | |
| | | 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 { staffOnJobListPage } from "@/api/personnelManagement/staffOnJob.js"; |
| | | import { listNotification, addNotification, updateNotification, delNotification,addOnlineMeeting,addFileSharing } from "@/api/collaborativeApproval/notificationManagement.js"; |
| | | import { id } from "element-plus/es/locales.mjs"; |
| | | |
| | | // 表åéªè¯è§å |
| | | const rules = { |
| | |
| | | tableLoading: false, |
| | | page: { |
| | | current: 1, |
| | | size: 100, |
| | | size: 20, |
| | | total: 0, |
| | | }, |
| | | tableData: [], |
| | |
| | | form: { |
| | | title: "", |
| | | type: "", |
| | | priority: "medium", |
| | | priority: "", |
| | | content: "", |
| | | departments: [], |
| | | expireDate: "", |
| | |
| | | fileList: [] |
| | | }); |
| | | |
| | | const { |
| | | searchForm, |
| | | tableLoading, |
| | | page, |
| | | tableData, |
| | | const { |
| | | searchForm, |
| | | tableLoading, |
| | | page, |
| | | tableData, |
| | | selectedIds, |
| | | form, |
| | | dialogVisible, |
| | |
| | | clickFun: (row) => { |
| | | publishNotification(row); |
| | | }, |
| | | // disabled: (row) => row.status === "published" |
| | | }, |
| | | { |
| | | name: "æ¤å", |
| | |
| | | clickFun: (row) => { |
| | | revokeNotification(row); |
| | | }, |
| | | // disabled: (row) => row.status !== "published" |
| | | } |
| | | ] |
| | | } |
| | | ]); |
| | | |
| | | // æ¨¡ææ°æ® |
| | | let mockData = [ |
| | | { |
| | | id: "1", |
| | | title: "2024å¹´æ¥èæ¾åéç¥", |
| | | type: "holiday", |
| | | priority: "high", |
| | | status: "published", |
| | | content: "æ ¹æ®å½å®¶è§å®ï¼ç»åå
¬å¸å®é
æ
åµï¼ç°å°2024å¹´æ¥èæ¾å宿éç¥å¦ä¸...", |
| | | departments: ["ææ¯é¨", "éå®é¨", "人äºé¨", "è´¢å¡é¨", "è¿è¥é¨", "å¸åºé¨", "客æé¨"], |
| | | expireDate: "2024-02-15", |
| | | syncMethods: ["wechat", "dingtalk", "email"], |
| | | createTime: "2024-01-15 10:30:00" |
| | | }, |
| | | { |
| | | id: "2", |
| | | title: "ææ¯é¨å¨ä¾ä¼éç¥", |
| | | type: "meeting", |
| | | priority: "medium", |
| | | status: "published", |
| | | content: "ææ¯é¨å®äºæ¯å¨äºä¸å2ç¹å¬å¼å¨ä¾ä¼ï¼è¯·åä½åäºåæ¶åå ...", |
| | | departments: ["ææ¯é¨"], |
| | | expireDate: "2024-01-20", |
| | | syncMethods: ["wechat", "dingtalk"], |
| | | createTime: "2024-01-14 15:20:00" |
| | | }, |
| | | { |
| | | id: "3", |
| | | title: "åå·¥è¡ä¸ºè§èå¤ç½éç¥", |
| | | type: "penalty", |
| | | priority: "high", |
| | | status: "draft", |
| | | content: "为维æ¤å
¬å¸æ£å¸¸ç§©åºï¼è§èåå·¥è¡ä¸ºï¼ç°å¯¹è¿åå
¬å¸è§å®çè¡ä¸ºè¿è¡å¤ç½...", |
| | | departments: ["人äºé¨", "ææ¯é¨", "éå®é¨"], |
| | | expireDate: "2024-02-13", |
| | | syncMethods: ["wechat", "email"], |
| | | createTime: "2024-01-13 09:15:00" |
| | | } |
| | | ]; |
| | | |
| | | // éç¥æ 颿¨¡æ¿ |
| | | const titleTemplates = [ |
| | | "å
³äº{year}å¹´{holiday}æ¾å宿çéç¥", |
| | |
| | | employeesLoading.value = true; |
| | | // ä¼å
使ç¨ç³»ç»ç¨æ·æ¥å£ï¼æç§æ·è·åï¼ |
| | | const userResponse = await userListNoPageByTenantId(); |
| | | |
| | | |
| | | if (userResponse.data) { |
| | | employees.value = userResponse.data.map(user => ({ |
| | | label: user.nickName || user.userName || 'æªç¥å§å', |
| | |
| | | })).filter(user => user.status === '0'); // åªæ¾ç¤ºæ£å¸¸ç¶æçç¨æ· |
| | | } else { |
| | | // å¦æç³»ç»ç¨æ·æ¥å£å¤±è´¥ï¼ä½¿ç¨åå·¥å°è´¦æ¥å£ |
| | | const response = await staffOnJobListPage({ |
| | | pageNum: 1, |
| | | pageSize: 1000, |
| | | 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 || 'æªç¥å§å', |
| | |
| | | } |
| | | groups[dept].push(employee); |
| | | }); |
| | | |
| | | |
| | | // æé¨é¨åç§°æåºï¼ç¡®ä¿æ¾ç¤ºé¡ºåºä¸è´ |
| | | return Object.keys(groups) |
| | | .sort() |
| | |
| | | const filterEmployees = (query) => { |
| | | if (query !== '') { |
| | | const lowerQuery = query.toLowerCase(); |
| | | return employees.value.filter(employee => |
| | | return employees.value.filter(employee => |
| | | employee.label.toLowerCase().includes(lowerQuery) || |
| | | employee.dept.toLowerCase().includes(lowerQuery) || |
| | | (employee.phone && employee.phone.includes(query)) || |
| | |
| | | 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 getEmployeeInfo = (employeeId) => { |
| | | const employee = employees.value.find(emp => emp.value === employeeId); |
| | | if (!employee) return null; |
| | | |
| | | |
| | | return { |
| | | name: employee.label, |
| | | dept: employee.dept, |
| | |
| | | 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('{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, |
| | |
| | | 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}`); |
| | | }; |
| | | |
| | |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | |
| | | setTimeout(() => { |
| | | let filteredData = [...mockData]; |
| | | |
| | | if (searchForm.value.title) { |
| | | filteredData = filteredData.filter(item => |
| | | item.title.toLowerCase().includes(searchForm.value.title.toLowerCase()) |
| | | ); |
| | | } |
| | | |
| | | if (searchForm.value.type) { |
| | | filteredData = filteredData.filter(item => item.type === searchForm.value.type); |
| | | } |
| | | |
| | | tableData.value = filteredData; |
| | | page.value.total = filteredData.length; |
| | | listNotification({...page.value, ...searchForm.value}) |
| | | .then(res => { |
| | | tableLoading.value = false; |
| | | }, 500); |
| | | tableData.value = res.data.records |
| | | page.value.total = res.data.total; |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | }) |
| | | }; |
| | | |
| | | // å页å¤ç |
| | |
| | | dialogTitle.value = "æ°å¢éç¥"; |
| | | // é置表å |
| | | Object.assign(form.value, { |
| | | id: "", |
| | | title: "", |
| | | type: "", |
| | | priority: "medium", |
| | | 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 || [] |
| | | }); |
| | | } |
| | |
| | | const submitForm = async () => { |
| | | try { |
| | | await formRef.value.validate(); |
| | | |
| | | |
| | | if (dialogType.value === "add") { |
| | | // æ°å¢éç¥ |
| | | const newNotification = { |
| | | id: (mockData.length + 1).toString(), |
| | | title: form.value.title, |
| | | type: form.value.type, |
| | | priority: form.value.priority, |
| | | status: "draft", |
| | | content: form.value.content, |
| | | departments: form.value.departments, |
| | | expireDate: form.value.expireDate, |
| | | syncMethods: form.value.syncMethods, |
| | | createTime: new Date().toLocaleString() |
| | | }; |
| | | |
| | | mockData.unshift(newNotification); |
| | | ElMessage.success("éç¥å建æå"); |
| | | addNotification({...form.value}).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("æ·»å æå"); |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | } else { |
| | | // ç¼è¾éç¥ |
| | | const index = mockData.findIndex(item => item.id === selectedIds.value[0]); |
| | | if (index !== -1) { |
| | | Object.assign(mockData[index], { |
| | | title: form.value.title, |
| | | type: form.value.type, |
| | | priority: form.value.priority, |
| | | content: form.value.content, |
| | | departments: form.value.departments, |
| | | expireDate: form.value.expireDate, |
| | | syncMethods: form.value.syncMethods |
| | | }); |
| | | ElMessage.success("éç¥æ´æ°æå"); |
| | | } |
| | | updateNotification({...form.value}).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("æ´æ°æå"); |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | } |
| | | |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | } catch (error) { |
| | | console.error("表åéªè¯å¤±è´¥:", error); |
| | | } |
| | |
| | | const createMeeting = async () => { |
| | | try { |
| | | await meetingFormRef.value.validate(); |
| | | |
| | | |
| | | // 模æå建ä¼è®® |
| | | const meetingInfo = { |
| | | title: meetingForm.value.title, |
| | |
| | | duration: meetingForm.value.duration, |
| | | participants: meetingForm.value.participants, |
| | | description: meetingForm.value.description, |
| | | platform: meetingForm.value.platform, |
| | | meetingId: `MTG${Date.now()}` |
| | | 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}åééç¥`); |
| | | meetingDialogVisible.value = false; |
| | | |
| | | // è·ååä¼äººåä¿¡æ¯ |
| | | // 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); |
| | |
| | | email: employee.email |
| | | } : null; |
| | | }).filter(Boolean); |
| | | |
| | | |
| | | // å°ä¼è®®ä¿¡æ¯æ·»å å°éç¥å表 |
| | | const meetingNotification = { |
| | | id: (mockData.length + 1).toString(), |
| | | 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}人`, |
| | | content: `ä¼è®®æ¶é´: ${meetingInfo.startTime}ï¼æ¶é¿: ${meetingInfo.duration}åéï¼å¹³å°: ${meetingPlatforms.find(p => p.value === meetingForm.value.platform)?.label || "æªç¥å¹³å°"}ï¼åä¼äººå: ${participantNames}ï¼å
±${participantDetails.length}人`, |
| | | departments: [], |
| | | expireDate: "", |
| | | syncMethods: [meetingForm.value.platform], |
| | | createTime: new Date().toLocaleString() |
| | | syncMethods: [meetingForm.value.platform] |
| | | }; |
| | | |
| | | mockData.unshift(meetingNotification); |
| | | getList(); |
| | | 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); |
| | | } |
| | |
| | | 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); |
| | | fileShareForm.value.files.push(fileInfo.name); |
| | | return false; // 黿¢èªå¨ä¸ä¼ |
| | | }; |
| | | |
| | |
| | | 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, |
| | | shareId: `FILE${Date.now()}` |
| | | }; |
| | | |
| | | ElMessage.success(`æä»¶å
±äº«æåï¼å
±äº«ID: ${shareInfo.shareId}ï¼å·²éç¥ç¸å
³é¨é¨`); |
| | | fileShareDialogVisible.value = false; |
| | | |
| | | 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 = { |
| | | id: (mockData.length + 1).toString(), |
| | | title: `[æä»¶å
񄧮] ${shareInfo.title}`, |
| | | type: "temporary", |
| | | priority: "medium", |
| | |
| | | departments: shareInfo.departments, |
| | | expireDate: "", |
| | | syncMethods: ["wechat", "dingtalk"], |
| | | createTime: new Date().toLocaleString() |
| | | }; |
| | | |
| | | mockData.unshift(fileShareNotification); |
| | | getList(); |
| | | 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) => { |
| | | row.status = "published"; |
| | | ElMessage.success("éç¥å叿å"); |
| | | getList(); |
| | | 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){ |
| | | ElMessage.success("éç¥å叿å"); |
| | | getList(); |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | }; |
| | | |
| | | // æ¤åéç¥ |
| | | const revokeNotification = (row) => { |
| | | row.status = "draft"; |
| | | ElMessage.success("éç¥å·²æ¤å"); |
| | | getList(); |
| | | 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){ |
| | | ElMessage.success("éç¥å·²æ¤å"); |
| | | getList(); |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | }; |
| | | |
| | | // å é¤éç¥ |
| | | const handleDelete = () => { |
| | | if (selectedIds.value.length === 0) { |
| | | let ids = []; |
| | | if (selectedIds.value.length > 0) { |
| | | ids = selectedIds.value; |
| | | }else{ |
| | | ElMessage.warning("è¯·éæ©è¦å é¤çéç¥"); |
| | | return; |
| | | } |
| | | |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | ElMessage.success("å 餿å"); |
| | | selectedIds.value = []; |
| | | getList(); |
| | | delNotification(ids).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("å 餿å"); |
| | | selectedIds.value = []; |
| | | getList(); |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | }).catch(() => { |
| | | // ç¨æ·åæ¶ |
| | | }); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | |
| | | <!-- ç³è¯·ç±»åéæ© --> |
| | | <el-card class="type-card"> |
| | | <div class="type-selector"> |
| | | <div |
| | | v-for="type in applicationTypes" |
| | | :key="type.value" |
| | | class="type-item" |
| | | :class="{ active: currentType === type.value }" |
| | | @click="changeType(type.value)" |
| | | > |
| | | <div class="type-icon"> |
| | | <el-icon :size="24"><component :is="type.icon"/></el-icon> |
| | | </div> |
| | | <div class="type-info"> |
| | | <div class="type-name">{{ type.name }}</div> |
| | | <div class="type-desc">{{ type.desc }}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- ä¼è®®ç³è¯·è¡¨å --> |
| | | <el-card> |
| | | <div class="form-header"> |
| | | <h3>{{ getCurrentTypeName() }}ç³è¯·</h3> |
| | | </div> |
| | | |
| | | <el-form |
| | | ref="meetingFormRef" |
| | | :model="meetingForm" |
| | | :rules="rules" |
| | | label-width="100px" |
| | | > |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¼è®®ä¸»é¢" prop="title"> |
| | | <el-input v-model="meetingForm.title" placeholder="请è¾å
¥ä¼è®®ä¸»é¢"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¼è®®å®¤" prop="roomId"> |
| | | <el-select v-model="meetingForm.roomId" placeholder="è¯·éæ©ä¼è®®å®¤" style="width: 100%"> |
| | | <el-option |
| | | v-for="room in meetingRooms" |
| | | :key="room.id" |
| | | :label="`${room.name} (${room.location})`" |
| | | :value="room.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="主æäºº" prop="host"> |
| | | <el-input v-model="meetingForm.host" placeholder="请è¾å
¥ä¸»æäººå§å"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <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> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <!-- 空åï¼ä¿æå¸å± --> |
| | | </el-col> |
| | | </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-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-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-form-item label="åä¼äººå" prop="participants"> |
| | | <el-select |
| | | v-model="meetingForm.participants" |
| | | multiple |
| | | filterable |
| | | placeholder="è¯·éæ©åä¼äººå" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="person in employees" |
| | | :key="person.id" |
| | | :label="`${person.staffName} (${person.postJob})`" |
| | | :value="person.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="ä¼è®®è¯´æ" prop="description"> |
| | | <el-input |
| | | v-model="meetingForm.description" |
| | | type="textarea" |
| | | :rows="4" |
| | | placeholder="请è¾å
¥ä¼è®®è¯´æ" |
| | | /> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <div class="form-footer"> |
| | | <el-button @click="resetForm">éç½®</el-button> |
| | | <el-button type="primary" @click="submitForm">æäº¤</el-button> |
| | | </div> |
| | | </el-card> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {ref, reactive, onMounted} from 'vue' |
| | | import {ElMessage} from 'element-plus' |
| | | import {Plus, Document, Promotion, Bell} from '@element-plus/icons-vue' |
| | | import {getRoomEnum, saveMeetingApplication} from '@/api/collaborativeApproval/meeting.js' |
| | | import {getStaffOnJob} from "@/api/personnelManagement/onboarding.js"; |
| | | |
| | | // å½åç³è¯·ç±»å |
| | | const currentType = ref('department') // approval: å®¡æ¹æµç¨, department: é¨é¨çº§, notification: éç¥åå¸ |
| | | |
| | | // ç³è¯·ç±»åé项 |
| | | const applicationTypes = ref([ |
| | | { |
| | | value: 'approval', |
| | | name: 'å®¡æ¹æµç¨ä¼è®®', |
| | | desc: 'éè¦ç»è¿å¤çº§å®¡æ¹çä¼è®®ç³è¯·', |
| | | icon: Document |
| | | }, |
| | | { |
| | | value: 'department', |
| | | name: 'é¨é¨çº§ä¼è®®', |
| | | desc: 'é¨é¨å
é¨ä¼è®®ç³è¯·æµç¨', |
| | | icon: Promotion |
| | | }, |
| | | { |
| | | value: 'notification', |
| | | name: 'ä¼è®®éç¥', |
| | | desc: 'æ é审æ¹ç´æ¥åå¸çä¼è®®éç¥', |
| | | icon: Bell |
| | | } |
| | | ]) |
| | | |
| | | // è¡¨åæ°æ® |
| | | const meetingForm = reactive({ |
| | | title: '', |
| | | type: '', |
| | | roomId: '', |
| | | host: '', |
| | | meetingDate: '', |
| | | startTime: '', |
| | | endTime: '', |
| | | participants: [], |
| | | description: '' |
| | | }) |
| | | |
| | | // è¡¨åæ ¡éªè§å |
| | | 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'}], |
| | | participants: [{required: true, message: 'è¯·éæ©åä¼äººå', trigger: 'change'}] |
| | | } |
| | | |
| | | // 表åå¼ç¨ |
| | | const meetingFormRef = ref(null) |
| | | |
| | | // ä¼è®®å®¤å表 |
| | | const meetingRooms = ref([]) |
| | | |
| | | // åå·¥å表 |
| | | const employees = ref([]) |
| | | |
| | | // æ¶é´é项ï¼ä»¥åå°æ¶ä¸ºé´éï¼ |
| | | const timeOptions = ref([]) |
| | | |
| | | // åå§åæ¶é´é项 |
| | | 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 changeType = (type) => { |
| | | currentType.value = type |
| | | } |
| | | |
| | | // è·åå½åç±»ååç§° |
| | | const getCurrentTypeName = () => { |
| | | const type = applicationTypes.value.find(t => t.value === currentType.value) |
| | | return type ? type.name : '' |
| | | } |
| | | |
| | | // é置表å |
| | | const resetForm = () => { |
| | | meetingFormRef.value?.resetFields() |
| | | } |
| | | |
| | | // æäº¤è¡¨å |
| | | const submitForm = () => { |
| | | meetingFormRef.value?.validate((valid) => { |
| | | if (valid) { |
| | | |
| | | let formData = {...meetingForm} |
| | | formData.applicationType = currentType.value |
| | | formData.startTime = `${meetingForm.meetingDate} ${meetingForm.startTime}:00` |
| | | formData.endTime = `${meetingForm.meetingDate} ${meetingForm.endTime}:00` |
| | | formData.participants = JSON.stringify(formData.participants) |
| | | console.log(formData) |
| | | saveMeetingApplication(formData).then(() => { |
| | | |
| | | // 模ææäº¤æä½ |
| | | ElMessage.success(`${getCurrentTypeName()}æäº¤æå`) |
| | | |
| | | // æ ¹æ®ä¸åç±»åæ§è¡ä¸åæä½ |
| | | switch (currentType.value) { |
| | | case 'approval': |
| | | ElMessage.info('ä¼è®®å·²æäº¤å®¡æ¹æµç¨') |
| | | break |
| | | case 'department': |
| | | ElMessage.info('é¨é¨çº§ä¼è®®ç³è¯·å·²æäº¤') |
| | | break |
| | | case 'notification': |
| | | ElMessage.info('ä¼è®®éç¥å·²åå¸') |
| | | break |
| | | } |
| | | resetForm() |
| | | }) |
| | | |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // 页é¢å è½½æ¶åå§å |
| | | onMounted(() => { |
| | | initTimeOptions() |
| | | getRoomEnum().then(res => { |
| | | meetingRooms.value = res.data |
| | | }) |
| | | getStaffOnJob().then(res => { |
| | | employees.value = res.data.sort((a, b) => a.postJob.localeCompare(b.postJob)) |
| | | }) |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .page-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | margin: 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .type-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .type-selector { |
| | | display: flex; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .type-item { |
| | | flex: 1; |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 20px; |
| | | border: 1px solid #ebeef5; |
| | | border-radius: 8px; |
| | | cursor: pointer; |
| | | transition: all 0.3s; |
| | | } |
| | | |
| | | .type-item:hover { |
| | | border-color: #409eff; |
| | | box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .type-item.active { |
| | | border-color: #409eff; |
| | | background-color: #ecf5ff; |
| | | } |
| | | |
| | | .type-icon { |
| | | margin-right: 15px; |
| | | color: #409eff; |
| | | } |
| | | |
| | | .type-name { |
| | | font-size: 16px; |
| | | font-weight: 500; |
| | | color: #303133; |
| | | margin-bottom: 5px; |
| | | } |
| | | |
| | | .type-desc { |
| | | font-size: 14px; |
| | | color: #909399; |
| | | } |
| | | |
| | | .form-header { |
| | | margin-bottom: 20px; |
| | | padding-bottom: 15px; |
| | | border-bottom: 1px solid #ebeef5; |
| | | } |
| | | |
| | | .form-header h3 { |
| | | margin: 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .form-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | margin-top: 30px; |
| | | padding-top: 20px; |
| | | border-top: 1px solid #ebeef5; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <!-- 页颿 é¢ --> |
| | | <div class="page-header"> |
| | | <h2>ä¼è®®è稿</h2> |
| | | <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-item label="ä¼è®®ä¸»é¢"> |
| | | <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-form-item> |
| | | <el-form-item> |
| | | <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"> |
| | | <template #default="scope"> |
| | | {{ formatDateTime(scope.row.meetingTime) }} |
| | | </template> |
| | | </el-table-column> |
| | | <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"> |
| | | <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> |
| | | </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" |
| | | /> |
| | | </el-card> |
| | | |
| | | <!-- ä¼è®®è稿详æ
å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | title="ä¼è®®è稿详æ
" |
| | | v-model="detailDialogVisible" |
| | | width="800px" |
| | | > |
| | | <div v-if="currentDraft"> |
| | | <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"> |
| | | {{ 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-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-select> |
| | | </el-form-item> |
| | | <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> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <!-- 空åï¼ä¿æå¸å± --> |
| | | </el-col> |
| | | </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-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-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> |
| | | <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-form-item> |
| | | </el-form> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="editDialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="submitForm">ä¿ å</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </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) |
| | | |
| | | // æ»æ¡æ° |
| | | const total = ref(0) |
| | | |
| | | // è稿åè¡¨æ°æ® |
| | | const draftList = ref([]) |
| | | |
| | | // æ¥è¯¢åæ° |
| | | const queryParams = reactive({ |
| | | current: 1, |
| | | size: 10 |
| | | }) |
| | | |
| | | // æç´¢è¡¨å |
| | | const searchForm = reactive({ |
| | | title: '', |
| | | meetingDate: '' |
| | | }) |
| | | |
| | | // æ¯å¦æ¾ç¤ºå¯¹è¯æ¡ |
| | | const detailDialogVisible = ref(false) |
| | | const editDialogVisible = ref(false) |
| | | |
| | | const roomList = ref([]) |
| | | |
| | | // å¯¹è¯æ¡æ é¢ |
| | | const dialogTitle = ref('') |
| | | |
| | | // å½åæ¥ççè稿 |
| | | const currentDraft = ref(null) |
| | | |
| | | // 表åå¼ç¨ |
| | | const meetingFormRef = ref(null) |
| | | |
| | | // æ¶é´é项ï¼ä»¥åå°æ¶ä¸ºé´éï¼å·¥ä½æ¶é´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: '', |
| | | 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; |
| | | } |
| | | |
| | | .page-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | margin: 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .search-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .content-section h4 { |
| | | margin: 0 0 15px 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .mt-20 { |
| | | margin-top: 20px; |
| | | } |
| | | |
| | | .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; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | |
| | | <el-form :model="searchForm" inline> |
| | | <el-form-item label="ä¼è®®ä¸»é¢"> |
| | | <el-input v-model="searchForm.title" placeholder="请è¾å
¥ä¼è®®ä¸»é¢" clearable/> |
| | | </el-form-item> |
| | | <el-form-item label="ç³è¯·äºº"> |
| | | <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-option label="æªå®¡æ¹" value="2"/> |
| | | <el-option label="已忶" value="3"/> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <!-- ä¼è®®å®¡æ¹å表 --> |
| | | <el-card> |
| | | <el-table v-loading="loading" :data="approvalList" border :height="tableHeight"> |
| | | <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"> |
| | | <template #default="scope"> |
| | | {{ scope.row.participants.length }}人 |
| | | </template> |
| | | </el-table-column> |
| | | <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"> |
| | | <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> |
| | | </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" |
| | | /> |
| | | </el-card> |
| | | |
| | | <!-- ä¼è®®è¯¦æ
å¯¹è¯æ¡ --> |
| | | <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">{{ |
| | | currentMeeting.title |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç³è¯·äºº" label-class-name="nowrap-label">{{ |
| | | currentMeeting.applicant |
| | | }}</el-descriptions-item> |
| | | <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"> |
| | | {{ formatDateTime(currentMeeting.meetingTime) }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ä¼è®®å°ç¹" label-class-name="nowrap-label">{{ |
| | | currentMeeting.location |
| | | }}</el-descriptions-item> |
| | | <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-tag :type="getStatusType(currentMeeting.status)"> |
| | | {{ getStatusText(currentMeeting.status) }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <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> |
| | | |
| | | |
| | | <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;" |
| | | > |
| | | {{ 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" |
| | | > |
| | | <div v-if="currentMeeting"> |
| | | <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"> |
| | | {{ 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;" |
| | | > |
| | | {{ participant.name }} |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-show="false" class="approval-opinion mt-20"> |
| | | <h4>å®¡æ¹æè§</h4> |
| | | <el-input |
| | | v-model="approvalOpinion" |
| | | type="textarea" |
| | | placeholder="请è¾å
¥å®¡æ¹æè§" |
| | | :rows="4" |
| | | /> |
| | | </div> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <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> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {ref, reactive, onMounted} from 'vue' |
| | | import {ElMessage, ElMessageBox} from 'element-plus' |
| | | import Pagination from '@/components/Pagination/index.vue' |
| | | import {getRoomEnum, getExamineList,saveMeetingApplication} from '@/api/collaborativeApproval/meeting.js' |
| | | import {getStaffOnJob} from "@/api/personnelManagement/onboarding.js"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | // æ°æ®å表å è½½ç¶æ |
| | | const loading = ref(false) |
| | | |
| | | // æ»æ¡æ° |
| | | const total = ref(0) |
| | | |
| | | // è¡¨æ ¼é«åº¦ï¼æ ¹æ®çªå£é«åº¦èªéåºï¼ |
| | | const tableHeight = ref(window.innerHeight - 380) |
| | | const roomEnum = ref([]) |
| | | const staffList = ref([]) |
| | | // 审æ¹åè¡¨æ°æ® |
| | | const approvalList = ref([]) |
| | | |
| | | // æ¥è¯¢åæ° |
| | | const queryParams = reactive({ |
| | | current: 1, |
| | | size: 10 |
| | | }) |
| | | |
| | | // æç´¢è¡¨å |
| | | const searchForm = reactive({ |
| | | title: '', |
| | | applicant: '', |
| | | status: '' |
| | | }) |
| | | |
| | | // æ¯å¦æ¾ç¤ºå¯¹è¯æ¡ |
| | | const detailDialogVisible = ref(false) |
| | | const approvalDialogVisible = ref(false) |
| | | |
| | | // å½åæ¥ççä¼è®® |
| | | const currentMeeting = ref(null) |
| | | |
| | | // å®¡æ¹æè§ |
| | | const approvalOpinion = ref('') |
| | | |
| | | // æ¥è¯¢æ°æ® |
| | | const getList = async () => { |
| | | loading.value = true |
| | | let resp = await getExamineList({...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.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 |
| | | } |
| | | |
| | | // æç´¢æé®æä½ |
| | | const handleSearch = () => { |
| | | queryParams.pageNum = 1 |
| | | getList() |
| | | } |
| | | |
| | | // éç½®æç´¢è¡¨å |
| | | const resetSearch = () => { |
| | | Object.assign(searchForm, { |
| | | title: '', |
| | | applicant: '', |
| | | status: '' |
| | | }) |
| | | handleSearch() |
| | | } |
| | | |
| | | // æ¥ç详æ
|
| | | const viewDetail = (row) => { |
| | | currentMeeting.value = row |
| | | detailDialogVisible.value = true |
| | | } |
| | | |
| | | // å¤çå®¡æ¹ |
| | | const handleApproval = (row) => { |
| | | currentMeeting.value = row |
| | | approvalOpinion.value = '' |
| | | approvalDialogVisible.value = true |
| | | } |
| | | |
| | | // è·åç¶æç±»å |
| | | 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 formatDateTime = (dateTime) => { |
| | | if (!dateTime) return '' |
| | | return dateTime.replace(' ', '\n') |
| | | } |
| | | |
| | | // æäº¤å®¡æ¹ |
| | | const submitApproval = (status) => { |
| | | // if (status === 'approved' && !approvalOpinion.value.trim()) { |
| | | // ElMessage.warning('请填åå®¡æ¹æè§') |
| | | // return |
| | | // } |
| | | |
| | | ElMessageBox.confirm( |
| | | `确认${status === '1' ? 'éè¿' : 'ä¸éè¿'}该ä¼è®®ç³è¯·ï¼`, |
| | | '审æ¹ç¡®è®¤', |
| | | { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | } |
| | | ).then(() => { |
| | | saveMeetingApplication({ |
| | | id: currentMeeting.value.id, |
| | | status: status |
| | | }).then(resp=>{ |
| | | // æ´æ°ä¼è®®ç¶æ |
| | | currentMeeting.value.status = status |
| | | |
| | | ElMessage.success('å®¡æ¹æäº¤æå') |
| | | approvalDialogVisible.value = false |
| | | getList() |
| | | }) |
| | | |
| | | }).catch(() => { |
| | | }) |
| | | } |
| | | |
| | | // 页é¢å è½½æ¶è·åæ°æ® |
| | | onMounted(async () => { |
| | | const [resp1, resp2]= await Promise.all([getRoomEnum(), getStaffOnJob()]) |
| | | roomEnum.value = resp1.data |
| | | staffList.value = resp2.data |
| | | |
| | | await getList() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .page-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | margin: 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .search-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .content-section h4 { |
| | | margin: 0 0 15px 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .mt-20 { |
| | | margin-top: 20px; |
| | | } |
| | | |
| | | .participants-list { |
| | | min-height: 40px; |
| | | padding: 15px; |
| | | border-radius: 4px; |
| | | line-height: 1.6; |
| | | } |
| | | |
| | | .approval-opinion h4 { |
| | | margin: 0 0 15px 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .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; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <el-form :model="queryForm" label-width="80px" inline> |
| | | <el-form-item label="æ¥è¯¢æ¥æ"> |
| | | <el-date-picker |
| | | v-model="queryForm.meetingDate" |
| | | type="date" |
| | | placeholder="è¯·éæ©æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | :clearable="false" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch">æ¥è¯¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <!-- ä¼è®®å®¤ä½¿ç¨æ
åµ --> |
| | | <el-card class="table-container" :loading="loading"> |
| | | <div class="time-table"> |
| | | <!-- 表头 --> |
| | | <div class="table-header"> |
| | | <div class="header-cell room-header">ä¼è®®å®¤</div> |
| | | <div |
| | | v-for="timeSlot in timeSlots" |
| | | :key="timeSlot.value" |
| | | class="header-cell time-header" |
| | | > |
| | | {{ timeSlot.label }} |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- è¡¨æ ¼å
容 --> |
| | | <div class="table-body"> |
| | | <div |
| | | v-for="room in roomUsage" |
| | | :key="room.id" |
| | | class="table-row" |
| | | > |
| | | <div class="cell room-cell">{{ room.name }}</div> |
| | | <div class="cells-container"> |
| | | <template v-for="(cell, index) in generateMeetingCells(room)" :key="index"> |
| | | <div |
| | | class="cell content-cell" |
| | | :class="[cell.type, `status-${cell.meeting?.status || '0'}`]" |
| | | :style="{ flex: cell.span-0.2 }" |
| | | @click="viewMeetingDetails(cell)" |
| | | > |
| | | <div v-if="cell.type === 'meeting'" class="meeting-content"> |
| | | <div class="meeting-title">{{ cell.meeting.title }}</div> |
| | | <div class="meeting-time">{{ cell.startTime }}-{{ cell.endTime }}</div> |
| | | </div> |
| | | <div v-else class="free-content"> |
| | | ç©ºé² |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- ä¼è®®è¯¦æ
å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | title="ä¼è®®è¯¦æ
" |
| | | v-model="detailDialogVisible" |
| | | width="800px" |
| | | > |
| | | <div v-if="currentMeeting"> |
| | | <el-descriptions :column="1" border> |
| | | <el-descriptions-item label="ä¼è®®ä¸»é¢">{{ currentMeeting.title }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä¼è®®å®¤">{{ currentMeeting.room }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä¼è®®æ¶é´">{{ currentMeeting.time }}</el-descriptions-item> |
| | | <el-descriptions-item label="主æäºº">{{ currentMeeting.host }}</el-descriptions-item> |
| | | <el-descriptions-item label="åä¼äººæ°">{{ currentMeeting.participants }}人</el-descriptions-item> |
| | | <el-descriptions-item label="ä¼è®®è¯´æ">{{ currentMeeting.description }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | </div> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="detailDialogVisible = false">å
³ é</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {ref, reactive, onMounted} from 'vue' |
| | | import {ElMessage} from 'element-plus' |
| | | import {getMeetingUseList} from "@/api/collaborativeApproval/meeting.js" |
| | | import dayjs from "dayjs"; |
| | | |
| | | // æ¥è¯¢è¡¨å |
| | | const queryForm = reactive({ |
| | | meetingDate: dayjs().format('YYYY-MM-DD') |
| | | }) |
| | | let loading = ref(false) |
| | | // æ¶é´æ®µï¼ä»¥åå°æ¶ä¸ºé´éï¼ |
| | | const timeSlots = ref([]) |
| | | |
| | | // ä¼è®®å®¤ä½¿ç¨æ
åµ |
| | | const roomUsage = ref([]) |
| | | |
| | | // å½åæ¥ççä¼è®® |
| | | const currentMeeting = ref(null) |
| | | |
| | | // æ¯å¦æ¾ç¤ºè¯¦æ
å¯¹è¯æ¡ |
| | | const detailDialogVisible = ref(false) |
| | | |
| | | // åå§åæ¶é´æ§½ï¼ä»¥åå°æ¶ä¸ºé´éï¼ä»8:00å°18:00ï¼ |
| | | const initTimeSlots = () => { |
| | | const slots = [] |
| | | for (let hour = 8; hour < 18; hour++) { |
| | | // æ¯ä¸ªå°æ¶æ·»å 两个æ¶é´æ®µï¼æ´ç¹ååç¹ |
| | | slots.push({ |
| | | label: `${hour.toString().padStart(2, '0')}:00`, |
| | | value: `${hour.toString().padStart(2, '0')}:00` |
| | | }) |
| | | |
| | | if (hour < 18) { // å°17:30ä¸ºæ¢ |
| | | slots.push({ |
| | | label: `${hour.toString().padStart(2, '0')}:30`, |
| | | value: `${hour.toString().padStart(2, '0')}:30` |
| | | }) |
| | | } |
| | | } |
| | | timeSlots.value = slots |
| | | } |
| | | |
| | | // çæä¼è®®å®¤çæ¶é´åå
æ ¼ |
| | | const generateMeetingCells = (room) => { |
| | | const cells = [] |
| | | const meetings = room.meetings || [] |
| | | const occupiedSlots = new Set() |
| | | |
| | | // å¤çæ¯ä¸ªä¼è®® |
| | | for (const meeting of meetings) { |
| | | |
| | | const startIdx = timeSlots.value.findIndex(slot => slot.value === meeting.startTime) |
| | | let endIdx = timeSlots.value.findIndex(slot => slot.value === meeting.endTime) |
| | | if (endIdx === -1) { |
| | | endIdx = timeSlots.value.length |
| | | } |
| | | console.log('endIdx111', endIdx) |
| | | if (startIdx !== -1 && endIdx !== -1) { |
| | | // æ 记被å ç¨çæ¶é´æ®µ |
| | | for (let i = startIdx; i < endIdx; i++) { |
| | | occupiedSlots.add(timeSlots.value[i].value) |
| | | } |
| | | |
| | | // å建ä¼è®®åå
æ ¼ |
| | | cells.push({ |
| | | type: 'meeting', |
| | | meeting: meeting, |
| | | span: endIdx - startIdx, |
| | | startTime: meeting.startTime, |
| | | endTime: meeting.endTime |
| | | }) |
| | | } |
| | | } |
| | | |
| | | // å¤çç©ºé²æ¶é´æ®µ |
| | | for (let i = 0; i < timeSlots.value.length; i++) { |
| | | const slot = timeSlots.value[i] |
| | | if (!occupiedSlots.has(slot.value)) { |
| | | // æ¥æ¾è¿ç»çç©ºé²æ¶é´æ®µ |
| | | let span = 1 |
| | | while (i + span < timeSlots.value.length && |
| | | !occupiedSlots.has(timeSlots.value[i + span].value)) { |
| | | occupiedSlots.add(timeSlots.value[i + span].value) |
| | | span++ |
| | | } |
| | | |
| | | cells.push({ |
| | | type: 'free', |
| | | span: span, |
| | | time: slot.value |
| | | }) |
| | | } |
| | | } |
| | | |
| | | // ææ¶é´æåº |
| | | cells.sort((a, b) => { |
| | | const timeA = a.startTime || a.time |
| | | const timeB = b.startTime || b.time |
| | | return timeSlots.value.findIndex(s => s.value === timeA) - |
| | | timeSlots.value.findIndex(s => s.value === timeB) |
| | | }) |
| | | console.log('cells', cells) |
| | | return cells |
| | | } |
| | | |
| | | // æ¥çä¼è®®è¯¦æ
|
| | | const viewMeetingDetails = (cell) => { |
| | | if (cell && cell.type === 'meeting') { |
| | | currentMeeting.value = cell.meeting |
| | | detailDialogVisible.value = true |
| | | } else { |
| | | ElMessage.info('该æ¶é´æ®µä¼è®®å®¤ç©ºé²') |
| | | } |
| | | } |
| | | |
| | | // æ¥è¯¢æé®æä½ |
| | | const handleSearch = async () => { |
| | | loading.value = true |
| | | let resp = await getMeetingUseList({...queryForm}) |
| | | roomUsage.value = resp.data |
| | | loading.value = false |
| | | } |
| | | |
| | | // éç½®æç´¢è¡¨å |
| | | const resetSearch = () => { |
| | | queryForm.date = dayjs().format('YYYY-MM-DD') |
| | | } |
| | | |
| | | // 页é¢å è½½æ¶è·åæ°æ® |
| | | onMounted(() => { |
| | | // åå§åæ¶é´æ§½ |
| | | initTimeSlots() |
| | | |
| | | // é»è®¤æ¥è¯¢ä»å¤©çæ°æ® |
| | | const today = new Date() |
| | | queryForm.date = today.toISOString().split('T')[0] |
| | | handleSearch() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .page-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | margin: 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .search-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .table-container { |
| | | padding: 0; |
| | | } |
| | | |
| | | .time-table { |
| | | width: 100%; |
| | | border-collapse: collapse; |
| | | } |
| | | |
| | | .table-header { |
| | | display: flex; |
| | | border: 1px solid; |
| | | } |
| | | |
| | | .table-row { |
| | | display: flex; |
| | | border: 1px solid #ebeef5; |
| | | border-top: none; |
| | | } |
| | | |
| | | .header-cell { |
| | | padding: 12px 5px; |
| | | text-align: center; |
| | | font-weight: bold; |
| | | border-right: 1px solid; |
| | | min-height: 20px; |
| | | } |
| | | |
| | | .room-header { |
| | | width: 120px; |
| | | } |
| | | |
| | | .time-header { |
| | | flex: 1; |
| | | } |
| | | |
| | | .cell { |
| | | padding: 15px 5px; |
| | | text-align: center; |
| | | border-right: 1px solid; |
| | | min-height: 20px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | word-break: break-word; |
| | | line-height: 1.2; |
| | | } |
| | | |
| | | .room-cell { |
| | | width: 120px; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .cells-container { |
| | | flex: 1; |
| | | display: flex; |
| | | } |
| | | |
| | | .content-cell { |
| | | min-height: 60px; |
| | | cursor: pointer; |
| | | transition: all 0.3s; |
| | | } |
| | | |
| | | .content-cell:hover { |
| | | opacity: 0.8; |
| | | } |
| | | |
| | | .free { |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | .meeting { |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .status-1 { |
| | | background-color: #fef0f0; |
| | | color: #d14646; |
| | | } |
| | | |
| | | .status-0 { |
| | | background-color: #c7ddc8; |
| | | color: rgba(230, 162, 60, 0.29); |
| | | } |
| | | |
| | | .meeting-content { |
| | | width: 100%; |
| | | } |
| | | |
| | | .meeting-title { |
| | | font-weight: bold; |
| | | margin-bottom: 5px; |
| | | } |
| | | |
| | | .meeting-time { |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .free-content { |
| | | color: #909399; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | |
| | | <el-form :model="searchForm" inline> |
| | | <el-form-item label="ä¼è®®ä¸»é¢"> |
| | | <el-input v-model="searchForm.title" placeholder="请è¾å
¥ä¼è®®ä¸»é¢" clearable/> |
| | | </el-form-item> |
| | | <el-form-item label="ç³è¯·äºº"> |
| | | <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> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <!-- ä¼è®®åå¸å表 --> |
| | | <el-card> |
| | | <el-table v-loading="loading" :data="approvalList" border :height="tableHeight"> |
| | | <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"> |
| | | <template #default="scope"> |
| | | {{ scope.row.participants.length }}人 |
| | | </template> |
| | | </el-table-column> |
| | | <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"> |
| | | <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> |
| | | </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" |
| | | /> |
| | | </el-card> |
| | | |
| | | <!-- ä¼è®®è¯¦æ
å¯¹è¯æ¡ --> |
| | | <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">{{ |
| | | currentMeeting.title |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç³è¯·äºº" label-class-name="nowrap-label">{{ |
| | | currentMeeting.applicant |
| | | }}</el-descriptions-item> |
| | | <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"> |
| | | {{ formatDateTime(currentMeeting.meetingTime) }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ä¼è®®å°ç¹" label-class-name="nowrap-label">{{ |
| | | currentMeeting.location |
| | | }}</el-descriptions-item> |
| | | <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-tag :type="getStatusType(currentMeeting.status)"> |
| | | {{ getStatusText(currentMeeting.status) }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <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> |
| | | |
| | | |
| | | <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;" |
| | | > |
| | | {{ 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" |
| | | > |
| | | <div v-if="currentMeeting"> |
| | | <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"> |
| | | {{ 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;" |
| | | > |
| | | {{ participant.name }} |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="approval-opinion mt-20"> |
| | | <h4>å叿è§</h4> |
| | | <el-input |
| | | v-model="publishComment" |
| | | type="textarea" |
| | | placeholder="请è¾å
¥å叿è§" |
| | | :rows="4" |
| | | /> |
| | | </div> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <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> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </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"; |
| | | |
| | | // æ°æ®å表å è½½ç¶æ |
| | | const loading = ref(false) |
| | | |
| | | // æ»æ¡æ° |
| | | const total = ref(0) |
| | | |
| | | // è¡¨æ ¼é«åº¦ï¼æ ¹æ®çªå£é«åº¦èªéåºï¼ |
| | | const tableHeight = ref(window.innerHeight - 380) |
| | | const roomEnum = ref([]) |
| | | const staffList = ref([]) |
| | | // åå¸åè¡¨æ°æ® |
| | | const approvalList = ref([]) |
| | | |
| | | // æ¥è¯¢åæ° |
| | | const queryParams = reactive({ |
| | | current: 1, |
| | | size: 10 |
| | | }) |
| | | |
| | | // æç´¢è¡¨å |
| | | const searchForm = reactive({ |
| | | title: '', |
| | | applicant: '', |
| | | status: '' |
| | | }) |
| | | |
| | | // æ¯å¦æ¾ç¤ºå¯¹è¯æ¡ |
| | | const detailDialogVisible = ref(false) |
| | | const approvalDialogVisible = ref(false) |
| | | |
| | | // å½åæ¥ççä¼è®® |
| | | const currentMeeting = ref(null) |
| | | |
| | | // åå¸æè§ |
| | | 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})` |
| | | } |
| | | }) |
| | | |
| | | |
| | | return it |
| | | }) |
| | | total.value = resp.data.total |
| | | loading.value = false |
| | | } |
| | | |
| | | // æç´¢æé®æä½ |
| | | const handleSearch = () => { |
| | | queryParams.pageNum = 1 |
| | | getList() |
| | | } |
| | | |
| | | // éç½®æç´¢è¡¨å |
| | | const resetSearch = () => { |
| | | Object.assign(searchForm, { |
| | | title: '', |
| | | applicant: '', |
| | | status: '' |
| | | }) |
| | | handleSearch() |
| | | } |
| | | |
| | | // æ¥ç详æ
|
| | | const viewDetail = (row) => { |
| | | currentMeeting.value = row |
| | | detailDialogVisible.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 getStatusText = (status) => { |
| | | const statusMap = { |
| | | '0': 'å¾
åå¸', |
| | | '1': 'å·²åå¸', |
| | | '2': '已忶', |
| | | } |
| | | return statusMap[status] || 'æªç¥' |
| | | } |
| | | |
| | | // æ ¼å¼åæ¥ææ¶é´ |
| | | const formatDateTime = (dateTime) => { |
| | | if (!dateTime) return '' |
| | | return dateTime.replace(' ', '\n') |
| | | } |
| | | |
| | | // æäº¤åå¸ |
| | | const submitApproval = (status) => { |
| | | // if (status === 'approved' && !publishComment.value.trim()) { |
| | | // ElMessage.warning('请填åå叿è§') |
| | | // return |
| | | // } |
| | | |
| | | ElMessageBox.confirm( |
| | | `确认${status === '1' ? 'åå¸' : 'åæ¶'}该ä¼è®®ï¼`, |
| | | 'åå¸ç¡®è®¤', |
| | | { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | } |
| | | ).then(() => { |
| | | saveMeetingApplication({ |
| | | id: currentMeeting.value.id, |
| | | publishStatus: status, |
| | | publishComment: publishComment.value |
| | | }).then(resp=>{ |
| | | // æ´æ°ä¼è®®ç¶æ |
| | | currentMeeting.value.status = status |
| | | |
| | | ElMessage.success('åå¸æäº¤æå') |
| | | approvalDialogVisible.value = false |
| | | getList() |
| | | }) |
| | | |
| | | }).catch(() => { |
| | | }) |
| | | } |
| | | |
| | | // 页é¢å è½½æ¶è·åæ°æ® |
| | | onMounted(async () => { |
| | | const [resp1, resp2]= await Promise.all([getRoomEnum(), getStaffOnJob()]) |
| | | roomEnum.value = resp1.data |
| | | staffList.value = resp2.data |
| | | |
| | | await getList() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .page-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | margin: 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .search-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .content-section h4 { |
| | | margin: 0 0 15px 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .mt-20 { |
| | | margin-top: 20px; |
| | | } |
| | | |
| | | .participants-list { |
| | | min-height: 40px; |
| | | padding: 15px; |
| | | border-radius: 4px; |
| | | line-height: 1.6; |
| | | } |
| | | |
| | | .approval-opinion h4 { |
| | | margin: 0 0 15px 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .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; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <!-- æç´¢åºå --> |
| | | <el-form :model="searchForm" label-width="100px" class="search-form"> |
| | | <el-form-item label="ä¼è®®å®¤åç§°"> |
| | | <el-input v-model="searchForm.name" placeholder="请è¾å
¥ä¼è®®å®¤åç§°" clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="ä½ç½®"> |
| | | <el-input v-model="searchForm.location" placeholder="请è¾å
¥ä½ç½®" clearable /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </el-form-item> |
| | | <el-form-item class="search-actions"> |
| | | <el-button @click="handleExport">导åº</el-button> |
| | | <el-button type="primary" @click="handleAdd"> |
| | | <el-icon><Plus /></el-icon> |
| | | æ°å¢ä¼è®®å®¤ |
| | | </el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <!-- ä¼è®®å®¤å表 --> |
| | | <el-card> |
| | | <el-table v-loading="loading" :data="meetingRoomList" border :height="tableHeight"> |
| | | <el-table-column prop="name" label="ä¼è®®å®¤åç§°" align="center" /> |
| | | <el-table-column prop="location" label="ä½ç½®" align="center" /> |
| | | <el-table-column prop="capacity" label="容纳人æ°" align="center" /> |
| | | <el-table-column prop="equipment" label="设å¤é
ç½®" align="center"> |
| | | <template #default="scope"> |
| | | <el-tag v-for="item in scope.row.equipment" :key="item" style="margin-right: 5px; margin-bottom: 5px;"> |
| | | {{ item }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="status" label="ç¶æ" align="center" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 1 ? 'success' : 'danger'"> |
| | | {{ scope.row.status === 1 ? 'å¯ç¨' : 'ç¦ç¨' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" align="center" width="200"> |
| | | <template #default="scope"> |
| | | <el-button type="primary" link @click="handleEdit(scope.row)">ç¼è¾</el-button> |
| | | <el-button 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" |
| | | /> |
| | | </el-card> |
| | | |
| | | <!-- æ·»å /ç¼è¾å¯¹è¯æ¡ --> |
| | | <el-dialog :title="dialogTitle" v-model="dialogVisible" width="600px" @close="cancel"> |
| | | <el-form ref="meetingRoomFormRef" :model="meetingRoomForm" :rules="rules" label-width="100px"> |
| | | <el-form-item label="ä¼è®®å®¤åç§°" prop="name"> |
| | | <el-input v-model="meetingRoomForm.name" placeholder="请è¾å
¥ä¼è®®å®¤åç§°" /> |
| | | </el-form-item> |
| | | <el-form-item label="ä½ç½®" prop="location"> |
| | | <el-input v-model="meetingRoomForm.location" placeholder="请è¾å
¥ä¼è®®å®¤ä½ç½®" /> |
| | | </el-form-item> |
| | | <el-form-item label="容纳人æ°" prop="capacity"> |
| | | <el-input-number v-model="meetingRoomForm.capacity" :min="1" placeholder="请è¾å
¥å®¹çº³äººæ°" /> |
| | | </el-form-item> |
| | | <el-form-item label="设å¤é
ç½®" prop="equipment"> |
| | | <el-select v-model="meetingRoomForm.equipment" multiple placeholder="è¯·éæ©è®¾å¤é
ç½®" style="width: 100%"> |
| | | <el-option |
| | | v-for="item in equipmentOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ç¶æ" prop="status"> |
| | | <el-radio-group v-model="meetingRoomForm.status"> |
| | | <el-radio :label="1">å¯ç¨</el-radio> |
| | | <el-radio :label="0">ç¦ç¨</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input v-model="meetingRoomForm.remark" type="textarea" placeholder="请è¾å
¥å¤æ³¨" /> |
| | | </el-form-item> |
| | | </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> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, getCurrentInstance } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { Plus } from '@element-plus/icons-vue' |
| | | import Pagination from '@/components/Pagination/index.vue' |
| | | import {getMeetingRoomList,saveRoom,delRoom} from '@/api/collaborativeApproval/meeting.js' |
| | | |
| | | // æ°æ®å表å è½½ç¶æ |
| | | const loading = ref(false) |
| | | |
| | | // æ»æ¡æ° |
| | | const total = ref(0) |
| | | |
| | | // è¡¨æ ¼é«åº¦ï¼æ ¹æ®çªå£é«åº¦èªéåºï¼ |
| | | const tableHeight = ref(window.innerHeight - 380) |
| | | |
| | | // ä¼è®®å®¤åè¡¨æ°æ® |
| | | const meetingRoomList = ref([]) |
| | | |
| | | // æ¥è¯¢åæ° |
| | | const queryParams = reactive({ |
| | | current: 1, |
| | | size: 10 |
| | | }) |
| | | |
| | | // æç´¢è¡¨å |
| | | const searchForm = reactive({ |
| | | name: '', |
| | | location: '' |
| | | }) |
| | | |
| | | // å¯¹è¯æ¡æ é¢ |
| | | const dialogTitle = ref('') |
| | | |
| | | // æ¯å¦æ¾ç¤ºå¯¹è¯æ¡ |
| | | const dialogVisible = ref(false) |
| | | |
| | | // 设å¤é
ç½®é项 |
| | | const equipmentOptions = ref([ |
| | | { value: 'æå½±ä»ª', label: 'æå½±ä»ª' }, |
| | | { value: 'çµè§', label: 'çµè§' }, |
| | | { value: 'é³å', label: 'é³å' }, |
| | | { value: 'çµè¯', label: 'çµè¯' }, |
| | | { value: 'è§é¢ä¼è®®ç³»ç»', label: 'è§é¢ä¼è®®ç³»ç»' }, |
| | | { value: 'ç½æ¿', label: 'ç½æ¿' }, |
| | | { value: 'ååæ¿', label: 'ååæ¿' }, |
| | | { value: 'æ 线ç½ç»', label: 'æ 线ç½ç»' } |
| | | ]) |
| | | |
| | | // è¡¨åæ°æ® |
| | | const meetingRoomForm = reactive({ |
| | | id: undefined, |
| | | name: '', |
| | | location: '', |
| | | capacity: 10, |
| | | equipment: [], |
| | | status: 1, |
| | | remark: '' |
| | | }) |
| | | |
| | | // è¡¨åæ ¡éªè§å |
| | | const rules = { |
| | | name: [{ required: true, message: 'ä¼è®®å®¤åç§°ä¸è½ä¸ºç©º', trigger: 'blur' }], |
| | | location: [{ required: true, message: 'ä½ç½®ä¸è½ä¸ºç©º', trigger: 'blur' }], |
| | | capacity: [{ required: true, message: '容纳人æ°ä¸è½ä¸ºç©º', trigger: 'blur' }] |
| | | } |
| | | |
| | | // 表åå¼ç¨ |
| | | const meetingRoomFormRef = ref(null) |
| | | |
| | | // æ¥è¯¢æ°æ® |
| | | const getList = async () => { |
| | | loading.value = true |
| | | |
| | | let resp = await getMeetingRoomList({...searchForm,...queryParams}) |
| | | meetingRoomList.value = resp.data.records.map(it=>{ |
| | | it.equipment = it.equipment.split(',') |
| | | return it; |
| | | }) |
| | | total.value = resp.data.total |
| | | loading.value = false |
| | | |
| | | } |
| | | |
| | | // æç´¢æé®æä½ |
| | | const handleSearch = () => { |
| | | queryParams.current = 1 |
| | | getList() |
| | | } |
| | | |
| | | // éç½®æç´¢è¡¨å |
| | | const resetSearch = () => { |
| | | Object.assign(searchForm, { |
| | | name: '', |
| | | location: '' |
| | | }) |
| | | handleSearch() |
| | | } |
| | | |
| | | // æ·»å æé®æä½ |
| | | const handleAdd = () => { |
| | | dialogTitle.value = 'æ·»å ä¼è®®å®¤' |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | // ä¿®æ¹æé®æä½ |
| | | const handleEdit = (row) => { |
| | | dialogTitle.value = 'ä¿®æ¹ä¼è®®å®¤' |
| | | Object.assign(meetingRoomForm, row) |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | // å é¤æé®æä½ |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm( |
| | | `æ¯å¦ç¡®è®¤å é¤ä¼è®®å®¤ "${row.name}"?`, |
| | | 'è¦å', |
| | | { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | } |
| | | ).then(() => { |
| | | // 模æå é¤æä½ |
| | | delRoom(row.id).then(resp=>{ |
| | | ElMessage.success('å 餿å') |
| | | getList() |
| | | }) |
| | | |
| | | }).catch(() => {}) |
| | | } |
| | | |
| | | // åæ¶æé® |
| | | const cancel = () => { |
| | | dialogVisible.value = false |
| | | reset() |
| | | } |
| | | |
| | | // 表åéç½® |
| | | const reset = () => { |
| | | Object.assign(meetingRoomForm, { |
| | | id: undefined, |
| | | name: '', |
| | | location: '', |
| | | capacity: 10, |
| | | equipment: [], |
| | | status: 1, |
| | | remark: '' |
| | | }) |
| | | meetingRoomFormRef.value?.resetFields() |
| | | } |
| | | |
| | | // æäº¤è¡¨å |
| | | const submitForm = () => { |
| | | meetingRoomFormRef.value?.validate((valid) => { |
| | | if (valid) { |
| | | // 模ææäº¤æä½ |
| | | |
| | | let formData = {... meetingRoomForm} |
| | | formData.equipment = formData.equipment.join(',') |
| | | saveRoom(formData).then(resp=>{ |
| | | ElMessage.success('ä¿åæå') |
| | | dialogVisible.value = false |
| | | getList() |
| | | }) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // å¯¼åº |
| | | const { proxy } = getCurrentInstance() |
| | | const handleExport = () => { |
| | | proxy.download('/meeting/export', { ...searchForm }, 'ä¼è®®å®¤è®¾ç½®.xlsx') |
| | | } |
| | | |
| | | // 页é¢å è½½æ¶è·åæ°æ® |
| | | onMounted(() => { |
| | | getList() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .search-form { |
| | | display: flex; |
| | | /* align-items: center; */ |
| | | } |
| | | |
| | | .search-actions { |
| | | margin-left: auto; |
| | | } |
| | | |
| | | .page-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | margin: 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .search-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | |
| | | <el-form :model="searchForm" inline> |
| | | <el-form-item label="ä¼è®®ä¸»é¢"> |
| | | <el-input v-model="searchForm.title" placeholder="请è¾å
¥ä¼è®®ä¸»é¢" clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="ç³è¯·äºº"> |
| | | <el-input v-model="searchForm.applicant" placeholder="请è¾å
¥ç³è¯·äºº" clearable /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <!-- ä¼è®®å表 --> |
| | | <el-card> |
| | | <el-table v-loading="loading" :data="meetingList" border :height="tableHeight"> |
| | | <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"> |
| | | <template #default="scope"> |
| | | {{ scope.row.participants.length }}人 |
| | | </template> |
| | | </el-table-column> |
| | | <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> |
| | | </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" |
| | | /> |
| | | </el-card> |
| | | |
| | | <!-- ä¼è®®è¯¦æ
å¯¹è¯æ¡ --> |
| | | <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">{{ |
| | | currentMeeting.title |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç³è¯·äºº" label-class-name="nowrap-label">{{ |
| | | currentMeeting.applicant |
| | | }}</el-descriptions-item> |
| | | <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"> |
| | | {{ formatDateTime(currentMeeting.meetingTime) }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ä¼è®®å°ç¹" label-class-name="nowrap-label">{{ |
| | | currentMeeting.location |
| | | }}</el-descriptions-item> |
| | | <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-tag :type="getStatusType(currentMeeting.status)"> |
| | | {{ getStatusText(currentMeeting.status) }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <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> |
| | | |
| | | <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;" |
| | | > |
| | | {{ 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" |
| | | > |
| | | <div v-if="currentMeeting"> |
| | | <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"> |
| | | {{ 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" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="minutesDialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="submitMinutes">ä¿ å</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </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" |
| | | |
| | | // æ°æ®å表å è½½ç¶æ |
| | | const loading = ref(false) |
| | | |
| | | // æ»æ¡æ° |
| | | const total = ref(0) |
| | | |
| | | // è¡¨æ ¼é«åº¦ï¼æ ¹æ®çªå£é«åº¦èªéåºï¼ |
| | | const tableHeight = ref(window.innerHeight - 380) |
| | | const roomEnum = ref([]) |
| | | const staffList = ref([]) |
| | | |
| | | // ä¼è®®åè¡¨æ°æ® |
| | | const meetingList = ref([]) |
| | | |
| | | // æ¥è¯¢åæ° |
| | | const queryParams = reactive({ |
| | | current: 1, |
| | | size: 10 |
| | | }) |
| | | |
| | | // æç´¢è¡¨å |
| | | const searchForm = reactive({ |
| | | title: '', |
| | | applicant: '', |
| | | // status: '1' // é»è®¤åªæ¾ç¤ºå·²éè¿å®¡æ¹çä¼è®® |
| | | }) |
| | | |
| | | // æ¯å¦æ¾ç¤ºå¯¹è¯æ¡ |
| | | const detailDialogVisible = ref(false) |
| | | const minutesDialogVisible = ref(false) |
| | | |
| | | // å½åæ¥ççä¼è®® |
| | | const currentMeeting = ref(null) |
| | | |
| | | // ä¼è®®çºªè¦å
容 |
| | | 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})` |
| | | } |
| | | }) |
| | | |
| | | return it |
| | | }) |
| | | total.value = resp.data.total |
| | | loading.value = false |
| | | } |
| | | |
| | | // æç´¢æé®æä½ |
| | | const handleSearch = () => { |
| | | queryParams.current = 1 |
| | | getList() |
| | | } |
| | | |
| | | // éç½®æç´¢è¡¨å |
| | | const resetSearch = () => { |
| | | Object.assign(searchForm, { |
| | | title: '', |
| | | applicant: '', |
| | | // status: '1' |
| | | }) |
| | | handleSearch() |
| | | } |
| | | |
| | | // æ¥ç详æ
|
| | | 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>` |
| | | } |
| | | |
| | | 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 handleCloseMinutesDialog = () => { |
| | | minutesContent.value = '' |
| | | } |
| | | |
| | | // è·åç¶æç±»å |
| | | 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 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 |
| | | |
| | | await getList() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .page-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | margin: 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .search-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .content-section h4 { |
| | | margin: 0 0 15px 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .mt-20 { |
| | | margin-top: 20px; |
| | | } |
| | | |
| | | .participants-list { |
| | | min-height: 40px; |
| | | padding: 15px; |
| | | border-radius: 4px; |
| | | line-height: 1.6; |
| | | } |
| | | |
| | | .nowrap-label { |
| | | white-space: nowrap !important; |
| | | } |
| | | |
| | | .editor-container { |
| | | border: 1px solid #dcdfe6; |
| | | border-radius: 4px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-card class="box-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>åå
¬ç©èµç³è¯·ç®¡ç</span> |
| | | <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-button> |
| | | </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"> |
| | | <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> |
| | | </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"> |
| | | <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"> |
| | | <template #default="scope"> |
| | | <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> |
| | | <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> |
| | | </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" |
| | | /> |
| | | </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-form-item> |
| | | <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-select> |
| | | </el-form-item> |
| | | <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> |
| | | <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-radio-group v-model="applyForm.urgency"> |
| | | <el-radio label="1">æ®é</el-radio> |
| | | <el-radio label="2">ç´§æ¥</el-radio> |
| | | <el-radio label="3">é常紧æ¥</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <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-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> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <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-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="ç³è¯·ç¶æ"> |
| | | <el-tag :type="getStatusType(currentDetail.status)"> |
| | | {{ getStatusText(currentDetail.status) }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <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="åæ¾æ¶é´">{{ currentDetail.issueTime || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="åæ¾äºº">{{ currentDetail.issueUser || '-' }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | </el-dialog> |
| | | </div> |
| | | </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' |
| | | |
| | | // ååºå¼æ°æ® |
| | | 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 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: '', |
| | | 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", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/officeSupplies/export", {}, "åå
¬ç©èµ.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | } |
| | | |
| | | // 页é¢å è½½æ¶è·åæ°æ® |
| | | onMounted(() => { |
| | | getList() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .mb8 { |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | |
| | | :deep(.el-descriptions__label) { |
| | | width: 120px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- 顶鍿使 --> |
| | | <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> |
| | | <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" |
| | | /> |
| | | </div> |
| | | <div class="right-actions"> |
| | | <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-col :span="6"> |
| | | <el-card class="overview-card"> |
| | | <div class="card-content"> |
| | | <div class="card-icon personal"> |
| | | <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" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="overview-card"> |
| | | <div class="card-content"> |
| | | <div class="card-icon group"> |
| | | <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" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="overview-card"> |
| | | <div class="card-content"> |
| | | <div class="card-icon department"> |
| | | <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" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="overview-card"> |
| | | <div class="card-content"> |
| | | <div class="card-icon company"> |
| | | <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" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- 计åå表 --> |
| | | <div class="plan-content"> |
| | | <el-card> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>{{ getCurrentLevelText() }} - {{ getCurrentPeriodText() }}</span> |
| | | <div> |
| | | <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 class="plan-header"> |
| | | <div class="plan-title"> |
| | | <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-dropdown @command="(command) => handleMoreAction(plan, command)"> |
| | | <el-button size="small"> |
| | | æ´å¤<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-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> |
| | | <span>{{ plan.startDate }} - {{ plan.endDate }}</span> |
| | | </div> |
| | | <div class="meta-item"> |
| | | <el-icon><User /></el-icon> |
| | | <span>{{ plan.assignee }}</span> |
| | | </div> |
| | | <div class="meta-item"> |
| | | <el-icon><Clock /></el-icon> |
| | | <span>è¿åº¦: {{ plan.progress }}%</span> |
| | | </div> |
| | | <div class="meta-item"> |
| | | <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" |
| | | /> |
| | | </div> |
| | | |
| | | <div class="plan-tags"> |
| | | <el-tag v-for="tag in plan.tags" :key="tag" size="small" style="margin-right: 5px"> |
| | | {{ tag }} |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | </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-form-item> |
| | | <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-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-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> |
| | | <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> |
| | | <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-checkbox-group v-model="planForm.tags"> |
| | | <el-checkbox label="all"></el-checkbox> |
| | | <el-checkbox label="manager">管çå±</el-checkbox> |
| | | <el-checkbox label="hr">人äºé¨é¨</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> |
| | | </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-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> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <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-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> |
| | | <el-descriptions-item label="æ¶é´å¨æ">{{ getCurrentPeriodText(currentPlanDetail.period) }}</el-descriptions-item> |
| | | <el-descriptions-item label="å¼å§æ¶é´">{{ currentPlanDetail.startDate }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç»ææ¶é´">{{ currentPlanDetail.endDate }}</el-descriptions-item> |
| | | <el-descriptions-item label="è´è´£äºº">{{ currentPlanDetail.assignee }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä¼å
级">{{ getPriorityText(currentPlanDetail.priority) }}</el-descriptions-item> |
| | | <el-descriptions-item label="æ ç¾">{{ currentPlanDetail.tags.join(', ') }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç¶æ">{{ getStatusText(currentPlanDetail.status) }}</el-descriptions-item> |
| | | <el-descriptions-item label="è¿åº¦">{{ currentPlanDetail.progress }}%</el-descriptions-item> |
| | | </el-descriptions> |
| | | </div> |
| | | </el-dialog> |
| | | </div> |
| | | </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' |
| | | |
| | | // ååºå¼æ°æ® |
| | | 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 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 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 handlePeriodChange = (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 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 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("å¤å¶å¤±è´¥ï¼è¯·æå¨å¤å¶"); |
| | | }); |
| | | // 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() |
| | | } |
| | | }) |
| | | }) |
| | | 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() |
| | | }) |
| | | } |
| | | } catch (error) { |
| | | console.log('表åéªè¯å¤±è´¥:', error) |
| | | } |
| | | } |
| | | |
| | | const handleDialogClose = () => { |
| | | planFormRef.value?.resetFields() |
| | | } |
| | | |
| | | const handleRefresh = () => { |
| | | getPlanList() |
| | | // ElMessage.success('æ°æ®å·²å·æ°') |
| | | } |
| | | |
| | | const handleFilter = () => { |
| | | ElMessage.info('æå¼çé颿¿') |
| | | } |
| | | |
| | | const handleExport = () => { |
| | | ElMessageBox.confirm("æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | // exportDutyPlan().then(res => { |
| | | |
| | | // }) |
| | | proxy.download("/dutyPlan/export", {}, "计å管ç.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | const handleShare = () => { |
| | | ElMessage.success('计åå·²å
񄧮') |
| | | } |
| | | |
| | | 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; |
| | | } |
| | | |
| | | .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; |
| | | } |
| | | |
| | | .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> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="process-tracking"> |
| | | <div class="header"> |
| | | <h2>è¿ç¨è¿½è¸ª</h2> |
| | | <el-button type="primary" @click="refreshData">å·æ°æ°æ®</el-button> |
| | | </div> |
| | | |
| | | <!-- 项ç®ç¶æç»è®¡ --> |
| | | <div class="status-cards"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="6" v-for="(item, index) in statusStats" :key="index"> |
| | | <el-card class="status-card" :class="item.type"> |
| | | <div class="card-content"> |
| | | <div class="card-icon"> |
| | | <el-icon :size="24"> |
| | | <component :is="item.icon" /> |
| | | </el-icon> |
| | | </div> |
| | | <div class="card-info"> |
| | | <div class="card-title">{{ item.label }}</div> |
| | | <div class="card-count">{{ item.count }}</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- 项ç®å表 --> |
| | | <el-card class="project-list"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>项ç®å表</span> |
| | | <el-button type="text" @click="toggleView"> |
| | | {{ viewMode === 'table' ? '忢å°çç¹å¾' : '忢å°å表' }} |
| | | </el-button> |
| | | </div> |
| | | </template> |
| | | |
| | | <!-- è¡¨æ ¼è§å¾ --> |
| | | <div v-if="viewMode === 'table'"> |
| | | <el-table :data="projectList" style="width: 100%"> |
| | | <el-table-column prop="name" label="项ç®åç§°" /> |
| | | <el-table-column prop="manager" label="è´è´£äºº"/> |
| | | <el-table-column label="ç¶æ" > |
| | | <template #default="{ row }"> |
| | | <el-tag :type="getStatusType(row.status)"> |
| | | {{ getStatusText(row.status) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="è¿åº¦" width="150"> |
| | | <template #default="{ row }"> |
| | | <el-progress :percentage="row.progress" :status="getProgressStatus(row.status)" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="startDate" label="å¼å§æ¶é´" width="120" /> |
| | | <el-table-column prop="endDate" label="ç»ææ¶é´" width="120" /> |
| | | <el-table-column label="æä½" width="150"> |
| | | <template #default="{ row }"> |
| | | <el-button type="text" @click="updateStatus(row)">æ´æ°ç¶æ</el-button> |
| | | <el-button type="text" @click="viewDetails(row)">详æ
</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <!-- çç¹å¾è§å¾ --> |
| | | <div v-else class="gantt-container"> |
| | | <div ref="ganttChart" style="width: 100%; height: 400px;"></div> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- ç¶ææ´æ°å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="statusDialogVisible" title="æ´æ°é¡¹ç®ç¶æ" width="400px"> |
| | | <el-form :model="statusForm" label-width="80px"> |
| | | <el-form-item label="项ç®åç§°"> |
| | | <el-input v-model="statusForm.name" disabled /> |
| | | </el-form-item> |
| | | <el-form-item label="å½åç¶æ"> |
| | | <el-select v-model="statusForm.status" placeholder="è¯·éæ©ç¶æ"> |
| | | <el-option label="æªå¼å§" value="not_started" /> |
| | | <el-option label="è¿è¡ä¸" value="in_progress" /> |
| | | <el-option label="已宿" value="completed" /> |
| | | <el-option label="å»¶æ" value="delayed" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="è¿åº¦"> |
| | | <el-slider v-model="statusForm.progress" :max="100" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="statusDialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="confirmStatusUpdate">确认</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, nextTick } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { Clock, Loading, Check, Warning } from '@element-plus/icons-vue' |
| | | import * as echarts from 'echarts' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const viewMode = ref('table') |
| | | const statusDialogVisible = ref(false) |
| | | const ganttChart = ref(null) |
| | | let chartInstance = null |
| | | |
| | | // ç¶æç»è®¡æ°æ® |
| | | const statusStats = reactive([ |
| | | { label: 'æªå¼å§', count: 3, type: 'not-started', icon: Clock }, |
| | | { label: 'è¿è¡ä¸', count: 5, type: 'in-progress', icon: Loading }, |
| | | { label: '已宿', count: 8, type: 'completed', icon: Check }, |
| | | { label: 'å»¶æ', count: 2, type: 'delayed', icon: Warning } |
| | | ]) |
| | | |
| | | // 项ç®åè¡¨æ°æ® |
| | | const projectList = reactive([ |
| | | { |
| | | id: 1, |
| | | name: 'æ±æéä¸ç产线æ©å»ºé¡¹ç®', |
| | | manager: 'éå¿å¼º', |
| | | status: 'completed', |
| | | progress: 100, |
| | | startDate: '2024-01-01', |
| | | endDate: '2024-01-15' |
| | | }, |
| | | { |
| | | id: 2, |
| | | name: 'æ°åç¯ä¿éç²å·¥èºç å', |
| | | manager: 'æéªå³°', |
| | | status: 'in_progress', |
| | | progress: 75, |
| | | startDate: '2024-01-10', |
| | | endDate: '2024-01-25' |
| | | }, |
| | | { |
| | | id: 3, |
| | | name: 'æ±æéä¸ERPç³»ç»å级', |
| | | manager: 'çé
çª', |
| | | status: 'in_progress', |
| | | progress: 60, |
| | | startDate: '2024-01-20', |
| | | endDate: '2024-02-10' |
| | | }, |
| | | { |
| | | id: 4, |
| | | name: 'ç¿å±±å¼é许å¯è¯ç»æç³è¯·', |
| | | manager: 'èµµä¼ä¸', |
| | | status: 'in_progress', |
| | | progress: 45, |
| | | startDate: '2024-01-25', |
| | | endDate: '2024-02-15' |
| | | }, |
| | | { |
| | | id: 5, |
| | | name: 'ç¯ä¿è®¾å¤å级æ¹é ', |
| | | manager: 'æä½³æ¬£', |
| | | status: 'delayed', |
| | | progress: 30, |
| | | startDate: '2024-01-15', |
| | | endDate: '2024-01-30' |
| | | }, |
| | | { |
| | | id: 6, |
| | | name: '年度å®å
¨ç产å¹è®è®¡å', |
| | | manager: 'å¼ å»ºå½', |
| | | status: 'not_started', |
| | | progress: 0, |
| | | startDate: '2024-02-01', |
| | | endDate: '2024-02-20' |
| | | } |
| | | ]) |
| | | |
| | | // ç¶ææ´æ°è¡¨å |
| | | const statusForm = reactive({ |
| | | id: null, |
| | | name: '', |
| | | status: '', |
| | | progress: 0 |
| | | }) |
| | | |
| | | // è·åç¶æç±»å |
| | | const getStatusType = (status) => { |
| | | const typeMap = { |
| | | not_started: 'info', |
| | | in_progress: 'warning', |
| | | completed: 'success', |
| | | delayed: 'danger' |
| | | } |
| | | return typeMap[status] || 'info' |
| | | } |
| | | |
| | | // è·åç¶æææ¬ |
| | | const getStatusText = (status) => { |
| | | const textMap = { |
| | | not_started: 'æªå¼å§', |
| | | in_progress: 'è¿è¡ä¸', |
| | | completed: '已宿', |
| | | delayed: 'å»¶æ' |
| | | } |
| | | return textMap[status] || 'æªç¥' |
| | | } |
| | | |
| | | // è·åè¿åº¦ç¶æ |
| | | const getProgressStatus = (status) => { |
| | | if (status === 'completed') return 'success' |
| | | if (status === 'delayed') return 'exception' |
| | | return null |
| | | } |
| | | |
| | | // 忢è§å¾æ¨¡å¼ |
| | | const toggleView = () => { |
| | | viewMode.value = viewMode.value === 'table' ? 'gantt' : 'table' |
| | | if (viewMode.value === 'gantt') { |
| | | nextTick(() => { |
| | | initGanttChart() |
| | | }) |
| | | } |
| | | } |
| | | |
| | | // åå§åçç¹å¾ |
| | | const initGanttChart = () => { |
| | | if (!ganttChart.value) return |
| | | |
| | | if (chartInstance) { |
| | | chartInstance.dispose() |
| | | } |
| | | |
| | | chartInstance = echarts.init(ganttChart.value) |
| | | |
| | | // åå¤çç¹å¾æ°æ® |
| | | const data = projectList.map((project, index) => ({ |
| | | name: project.name, |
| | | value: [ |
| | | index, |
| | | new Date(project.startDate).getTime(), |
| | | new Date(project.endDate).getTime(), |
| | | project.progress |
| | | ], |
| | | itemStyle: { |
| | | color: getGanttColor(project.status) |
| | | } |
| | | })) |
| | | |
| | | const option = { |
| | | title: { |
| | | text: '项ç®çç¹å¾', |
| | | left: 'center' |
| | | }, |
| | | tooltip: { |
| | | formatter: (params) => { |
| | | const project = projectList[params.value[0]] |
| | | return ` |
| | | <div> |
| | | <strong>${project.name}</strong><br/> |
| | | è´è´£äºº: ${project.manager}<br/> |
| | | ç¶æ: ${getStatusText(project.status)}<br/> |
| | | è¿åº¦: ${project.progress}%<br/> |
| | | å¼å§æ¶é´: ${project.startDate}<br/> |
| | | ç»ææ¶é´: ${project.endDate} |
| | | </div> |
| | | ` |
| | | } |
| | | }, |
| | | grid: { |
| | | left: '15%', |
| | | right: '10%', |
| | | top: '15%', |
| | | bottom: '15%' |
| | | }, |
| | | xAxis: { |
| | | type: 'time', |
| | | axisLabel: { |
| | | formatter: (value) => { |
| | | return echarts.format.formatTime('MM-dd', value) |
| | | } |
| | | } |
| | | }, |
| | | yAxis: { |
| | | type: 'category', |
| | | data: projectList.map(p => p.name), |
| | | inverse: true |
| | | }, |
| | | series: [{ |
| | | type: 'custom', |
| | | renderItem: (params, api) => { |
| | | const categoryIndex = api.value(0) |
| | | const start = api.coord([api.value(1), categoryIndex]) |
| | | const end = api.coord([api.value(2), categoryIndex]) |
| | | const height = api.size([0, 1])[1] * 0.6 |
| | | |
| | | return { |
| | | type: 'rect', |
| | | shape: { |
| | | x: start[0], |
| | | y: start[1] - height / 2, |
| | | width: end[0] - start[0], |
| | | height: height |
| | | }, |
| | | style: api.style() |
| | | } |
| | | }, |
| | | data: data |
| | | }] |
| | | } |
| | | |
| | | chartInstance.setOption(option) |
| | | } |
| | | |
| | | // è·åçç¹å¾é¢è² |
| | | const getGanttColor = (status) => { |
| | | const colorMap = { |
| | | not_started: '#909399', |
| | | in_progress: '#E6A23C', |
| | | completed: '#67C23A', |
| | | delayed: '#F56C6C' |
| | | } |
| | | return colorMap[status] || '#909399' |
| | | } |
| | | |
| | | // æ´æ°ç¶æ |
| | | const updateStatus = (project) => { |
| | | statusForm.id = project.id |
| | | statusForm.name = project.name |
| | | statusForm.status = project.status |
| | | statusForm.progress = project.progress |
| | | statusDialogVisible.value = true |
| | | } |
| | | |
| | | // ç¡®è®¤ç¶ææ´æ° |
| | | const confirmStatusUpdate = () => { |
| | | const project = projectList.find(p => p.id === statusForm.id) |
| | | if (project) { |
| | | project.status = statusForm.status |
| | | project.progress = statusForm.progress |
| | | |
| | | // æ´æ°ç»è®¡æ°æ® |
| | | updateStatusStats() |
| | | |
| | | // 妿æ¯çç¹å¾è§å¾ï¼éæ°æ¸²æ |
| | | if (viewMode.value === 'gantt') { |
| | | nextTick(() => { |
| | | initGanttChart() |
| | | }) |
| | | } |
| | | |
| | | ElMessage.success('ç¶ææ´æ°æå') |
| | | } |
| | | statusDialogVisible.value = false |
| | | } |
| | | |
| | | // æ´æ°ç¶æç»è®¡ |
| | | const updateStatusStats = () => { |
| | | const stats = { |
| | | not_started: 0, |
| | | in_progress: 0, |
| | | completed: 0, |
| | | delayed: 0 |
| | | } |
| | | |
| | | projectList.forEach(project => { |
| | | stats[project.status]++ |
| | | }) |
| | | |
| | | statusStats[0].count = stats.not_started |
| | | statusStats[1].count = stats.in_progress |
| | | statusStats[2].count = stats.completed |
| | | statusStats[3].count = stats.delayed |
| | | } |
| | | |
| | | // æ¥ç详æ
|
| | | const viewDetails = (project) => { |
| | | ElMessage.info(`æ¥ç项ç®è¯¦æ
: ${project.name}`) |
| | | } |
| | | |
| | | // å·æ°æ°æ® |
| | | const refreshData = () => { |
| | | // 模æå®æ¶æ´æ° |
| | | const randomProject = projectList[Math.floor(Math.random() * projectList.length)] |
| | | if (randomProject.status === 'in_progress') { |
| | | randomProject.progress = Math.min(100, randomProject.progress + Math.floor(Math.random() * 10)) |
| | | if (randomProject.progress === 100) { |
| | | randomProject.status = 'completed' |
| | | } |
| | | } |
| | | |
| | | updateStatusStats() |
| | | |
| | | if (viewMode.value === 'gantt') { |
| | | nextTick(() => { |
| | | initGanttChart() |
| | | }) |
| | | } |
| | | |
| | | ElMessage.success('æ°æ®å·²å·æ°') |
| | | } |
| | | |
| | | onMounted(() => { |
| | | updateStatusStats() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .process-tracking { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .header h2 { |
| | | margin: 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .status-cards { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .status-card { |
| | | cursor: pointer; |
| | | transition: all 0.3s; |
| | | } |
| | | |
| | | .status-card:hover { |
| | | transform: translateY(-2px); |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .card-content { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .card-icon { |
| | | margin-right: 15px; |
| | | padding: 10px; |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .status-card.not-started .card-icon { |
| | | background-color: #f4f4f5; |
| | | color: #909399; |
| | | } |
| | | |
| | | .status-card.in-progress .card-icon { |
| | | background-color: #fdf6ec; |
| | | color: #E6A23C; |
| | | } |
| | | |
| | | .status-card.completed .card-icon { |
| | | background-color: #f0f9ff; |
| | | color: #67C23A; |
| | | } |
| | | |
| | | .status-card.delayed .card-icon { |
| | | background-color: #fef0f0; |
| | | color: #F56C6C; |
| | | } |
| | | |
| | | .card-info { |
| | | flex: 1; |
| | | } |
| | | |
| | | .card-title { |
| | | font-size: 14px; |
| | | color: #909399; |
| | | margin-bottom: 5px; |
| | | } |
| | | |
| | | .card-count { |
| | | font-size: 24px; |
| | | font-weight: bold; |
| | | color: #303133; |
| | | } |
| | | |
| | | .project-list { |
| | | margin-top: 20px; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .gantt-container { |
| | | min-height: 400px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div> |
| | | <el-form :model="searchForm" :inline="true"> |
| | | <el-form-item label="ä¾åºååç§°ï¼"> |
| | | <el-input v-model="searchForm.supplierName" placeholder="请è¾å
¥" clearable prefix-icon="Search" |
| | | @change="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="éè´ååå·ï¼"> |
| | | <el-input |
| | | v-model="searchForm.purchaseContractNumber" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="éå®ååå·ï¼"> |
| | | <el-input v-model="searchForm.salesContractNo" placeholder="请è¾å
¥" clearable prefix-icon="Search" |
| | | @change="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="项ç®åç§°ï¼"> |
| | | <el-input v-model="searchForm.projectName" placeholder="请è¾å
¥" clearable prefix-icon="Search" |
| | | @change="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleQuery"> æç´¢ </el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | |
| | | </div> |
| | | <div class="table_list"> |
| | | <div style="display: flex;justify-content: flex-end;margin-bottom: 20px;"> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | | <el-table |
| | | :data="tableData" |
| | | border |
| | | v-loading="tableLoading" |
| | | @selection-change="handleSelectionChange" |
| | | :expand-row-keys="expandedRowKeys" |
| | | :row-key="(row) => row.id" |
| | | show-summary |
| | | :summary-method="summarizeMainTable" |
| | | @expand-change="expandChange" |
| | | height="calc(100vh - 18.5em)" |
| | | :row-class-name="tableRowClassName" |
| | | > |
| | | <el-table-column align="center" type="selection" width="55" /> |
| | | <el-table-column type="expand"> |
| | | <template #default="props"> |
| | | <el-table |
| | | :data="props.row.children" |
| | | border |
| | | show-summary |
| | | :summary-method="summarizeChildrenTable" |
| | | > |
| | | <el-table-column |
| | | align="center" |
| | | label="åºå·" |
| | | type="index" |
| | | width="60" |
| | | /> |
| | | <el-table-column label="产å大类" prop="productCategory" /> |
| | | <el-table-column label="è§æ ¼åå·" prop="specificationModel" /> |
| | | <el-table-column label="åä½" prop="unit" /> |
| | | <el-table-column label="æ°é" prop="quantity" /> |
| | | <el-table-column label="ç¨ç(%)" prop="taxRate" /> |
| | | <el-table-column |
| | | label="å«ç¨åä»·(å
)" |
| | | prop="taxInclusiveUnitPrice" |
| | | :formatter="formattedNumber" |
| | | /> |
| | | <el-table-column |
| | | label="å«ç¨æ»ä»·(å
)" |
| | | prop="taxInclusiveTotalPrice" |
| | | :formatter="formattedNumber" |
| | | /> |
| | | <el-table-column |
| | | label="ä¸å«ç¨æ»ä»·(å
)" |
| | | prop="taxExclusiveTotalPrice" |
| | | :formatter="formattedNumber" |
| | | /> |
| | | </el-table> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | <el-table-column |
| | | label="éè´ååå·" |
| | | prop="purchaseContractNumber" |
| | | width="200" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="éå®ååå·" |
| | | prop="salesContractNo" |
| | | width="200" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="ä¾åºååç§°" |
| | | width="240" |
| | | prop="supplierName" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column label="订åç¶æ" width="100" align="center"> |
| | | <template #default="scope"> |
| | | <el-tag v-if="scope.row.isInvalid" type="danger" size="small">失æ</el-tag> |
| | | <el-tag v-else type="success" size="small">æ£å¸¸</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | label="项ç®åç§°" |
| | | prop="projectName" |
| | | width="420" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="审æ¹ç¶æ" |
| | | prop="approvalStatus" |
| | | width="200" |
| | | show-overflow-tooltip |
| | | > |
| | | <template #default="scope"> |
| | | <el-tag |
| | | size="small" |
| | | > |
| | | {{ approvalStatusText[scope.row.approvalStatus] || 'æªç¥ç¶æ' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | label="仿¬¾æ¹å¼" |
| | | width="100" |
| | | prop="paymentMethod" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="ååéé¢(å
)" |
| | | prop="contractAmount" |
| | | width="200" |
| | | show-overflow-tooltip |
| | | :formatter="formattedNumber" |
| | | /> |
| | | <el-table-column |
| | | label="å½å
¥äºº" |
| | | prop="recorderName" |
| | | width="100" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="å½å
¥æ¥æ" |
| | | prop="entryDate" |
| | | width="100" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | fixed="right" |
| | | label="æä½" |
| | | min-width="150" |
| | | align="center" |
| | | > |
| | | <template #default="scope"> |
| | | <el-button |
| | | link |
| | | type="primary" |
| | | size="small" |
| | | @click="approvePurchase(scope.row)" |
| | | :disabled="scope.row.approvalStatus !== 0" |
| | | >审æ¹</el-button |
| | | > |
| | | <el-button |
| | | link |
| | | type="primary" |
| | | size="small" |
| | | @click="rejectPurchase(scope.row)" |
| | | :disabled="scope.row.approvalStatus !== 0" |
| | | >æç»å®¡æ¹</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> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { getToken } from "@/utils/auth"; |
| | | import pagination from "@/components/PIMTable/Pagination.vue"; |
| | | import { ref, onMounted, reactive, toRefs, getCurrentInstance, nextTick } from "vue"; |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { userListNoPage } from "@/api/system/user.js"; |
| | | import { |
| | | getSalesLedgerWithProducts, |
| | | addOrUpdateSalesLedgerProduct, |
| | | delProduct, |
| | | delLedgerFile, |
| | | getProductInfoByContractNo, |
| | | } from "@/api/salesManagement/salesLedger.js"; |
| | | import { |
| | | addOrEditPurchase, |
| | | delPurchase, |
| | | getSalesNo, |
| | | purchaseListPage, |
| | | productList, |
| | | getPurchaseById, |
| | | getOptions, |
| | | createPurchaseNo, updateApprovalStatus, |
| | | } from "@/api/procurementManagement/procurementLedger.js"; |
| | | import useFormData from "@/hooks/useFormData.js"; |
| | | import QRCode from "qrcode"; |
| | | |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const tableData = ref([]); |
| | | const productData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const productSelectedRows = ref([]); |
| | | const modelOptions = ref([]); |
| | | const userList = ref([]); |
| | | const productOptions = ref([]); |
| | | const salesContractList = ref([]); |
| | | const supplierList = ref([]); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }); |
| | | const total = ref(0); |
| | | const fileList = ref([]); |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { modelList, productTreeList } from "@/api/basicData/product.js"; |
| | | import dayjs from "dayjs"; |
| | | import { getCurrentDate } from "@/utils/index.js"; |
| | | |
| | | const userStore = useUserStore(); |
| | | |
| | | // äºç»´ç ç¸å
³åé |
| | | const qrCodeDialogVisible = ref(false); |
| | | const qrCodeUrl = ref(""); |
| | | |
| | | // 订å审æ¹ç¶ææ¾ç¤ºææ¬ |
| | | const approvalStatusText = { |
| | | 0: 'å¾
审æ¹', |
| | | 1: '审æ¹éè¿', |
| | | 2: '审æ¹å¤±è´¥' |
| | | }; |
| | | |
| | | // ç¨æ·ä¿¡æ¯è¡¨åå¼¹æ¡æ°æ® |
| | | const operationType = ref(""); |
| | | const dialogFormVisible = ref(false); |
| | | const data = reactive({ |
| | | searchForm: { |
| | | supplierName: "", // ä¾åºååç§° |
| | | purchaseContractNumber: "", // éè´ååç¼å· |
| | | salesContractNo: "", // éå®ååç¼å· |
| | | projectName: "", // 项ç®åç§° |
| | | entryDate: null, // å½å
¥æ¥æ |
| | | entryDateStart: undefined, |
| | | entryDateEnd: undefined, |
| | | }, |
| | | form: { |
| | | purchaseContractNumber: "", |
| | | salesLedgerId: "", |
| | | projectName: "", |
| | | recorderId: "", |
| | | entryDate: "", |
| | | productData: [], |
| | | supplierName: "", |
| | | supplierId: "", |
| | | paymentMethod: "", |
| | | executionDate: "", |
| | | approvalStatus: "0", |
| | | }, |
| | | rules: { |
| | | purchaseContractNumber: [ |
| | | { required: true, message: "请è¾å
¥", trigger: "blur" }, |
| | | ], |
| | | projectName: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | supplierId: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | entryDate: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | executionDate: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | }, |
| | | }); |
| | | const { form, rules } = toRefs(data); |
| | | const { form: searchForm } = useFormData(data.searchForm); |
| | | |
| | | // 产å表åå¼¹æ¡æ°æ® |
| | | const productFormVisible = ref(false); |
| | | const productOperationType = ref(""); |
| | | const productOperationIndex = ref(""); |
| | | const currentId = ref(""); |
| | | const productFormData = reactive({ |
| | | productForm: { |
| | | productId: "", |
| | | productCategory: "", |
| | | productModelId: "", |
| | | specificationModel: "", |
| | | unit: "", |
| | | quantity: "", |
| | | taxInclusiveUnitPrice: "", |
| | | taxRate: "", |
| | | taxInclusiveTotalPrice: "", |
| | | taxExclusiveTotalPrice: "", |
| | | invoiceType: "", |
| | | warnNum: "", |
| | | }, |
| | | productRules: { |
| | | productId: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | productModelId: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | unit: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | quantity: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | taxInclusiveUnitPrice: [ |
| | | { required: true, message: "请è¾å
¥", trigger: "blur" }, |
| | | ], |
| | | taxRate: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | warnNum: [{ required: false, message: "è¯·éæ©", trigger: "change" }], |
| | | taxInclusiveTotalPrice: [ |
| | | { required: true, message: "请è¾å
¥", trigger: "blur" }, |
| | | ], |
| | | taxExclusiveTotalPrice: [ |
| | | { required: true, message: "请è¾å
¥", trigger: "blur" }, |
| | | ], |
| | | invoiceType: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | }, |
| | | }); |
| | | const { productForm, productRules } = toRefs(productFormData); |
| | | const upload = reactive({ |
| | | // ä¸ä¼ çå°å |
| | | url: import.meta.env.VITE_APP_BASE_API + "/file/upload", |
| | | // 设置ä¸ä¼ ç请æ±å¤´é¨ |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | }); |
| | | |
| | | const changeDaterange = (value) => { |
| | | if (value) { |
| | | searchForm.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD"); |
| | | searchForm.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD"); |
| | | } else { |
| | | searchForm.entryDateStart = undefined; |
| | | searchForm.entryDateEnd = undefined; |
| | | } |
| | | handleQuery(); |
| | | }; |
| | | |
| | | const formattedNumber = (row, column, cellValue) => { |
| | | return parseFloat(cellValue).toFixed(2); |
| | | }; |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | // å表åè®¡æ¹æ³ |
| | | const summarizeChildrenTable = (param) => { |
| | | return proxy.summarizeTable( |
| | | param, |
| | | [ |
| | | "taxInclusiveUnitPrice", |
| | | "taxInclusiveTotalPrice", |
| | | "taxExclusiveTotalPrice", |
| | | "ticketsNum", |
| | | "ticketsAmount", |
| | | "futureTickets", |
| | | "futureTicketsAmount", |
| | | ], |
| | | { |
| | | ticketsNum: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | futureTickets: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | } |
| | | ); |
| | | }; |
| | | const paginationChange = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | const { entryDate, ...rest } = searchForm; |
| | | purchaseListPage({ ...rest, ...page }) |
| | | .then((res) => { |
| | | tableLoading.value = false; |
| | | // tableData.value = res.data.records; |
| | | // å¤çæ°æ®ï¼æ·»å 失æç¶ææ è®° |
| | | tableData.value = res.data.records.map(record => ({ |
| | | ...record, |
| | | isInvalid: record.isWhite === 1 |
| | | })); |
| | | tableData.value.map((item) => { |
| | | item.children = []; |
| | | }); |
| | | total.value = res.data.total; |
| | | expandedRowKeys.value = []; |
| | | }) |
| | | .catch(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | const productSelected = (selectedRows) => { |
| | | productSelectedRows.value = selectedRows; |
| | | }; |
| | | const expandedRowKeys = ref([]); |
| | | // å±å¼è¡ |
| | | const expandChange = (row, expandedRows) => { |
| | | if (expandedRows.length > 0) { |
| | | expandedRowKeys.value = []; |
| | | try { |
| | | productList({ salesLedgerId: row.id, type: 2 }).then((res) => { |
| | | const index = tableData.value.findIndex((item) => item.id === row.id); |
| | | if (index > -1) { |
| | | tableData.value[index].children = res.data; |
| | | } |
| | | expandedRowKeys.value.push(row.id); |
| | | }); |
| | | } catch (error) { |
| | | console.log(error); |
| | | } |
| | | } else { |
| | | expandedRowKeys.value = []; |
| | | } |
| | | }; |
| | | // 主表åè®¡æ¹æ³ |
| | | const summarizeMainTable = (param) => { |
| | | return proxy.summarizeTable(param, ["contractAmount"]); |
| | | }; |
| | | // å表åè®¡æ¹æ³ |
| | | const summarizeProTable = (param) => { |
| | | return proxy.summarizeTable(param, [ |
| | | "taxInclusiveUnitPrice", |
| | | "taxInclusiveTotalPrice", |
| | | "taxExclusiveTotalPrice", |
| | | ]); |
| | | }; |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = (type, row) => { |
| | | operationType.value = type; |
| | | form.value = {}; |
| | | productData.value = []; |
| | | fileList.value = []; |
| | | if (operationType.value == "add") { |
| | | createPurchaseNo().then((res) => { |
| | | form.value.purchaseContractNumber = res.data; |
| | | }); |
| | | } |
| | | userListNoPage().then((res) => { |
| | | userList.value = res.data; |
| | | }); |
| | | getSalesNo().then((res) => { |
| | | salesContractList.value = res; |
| | | }); |
| | | getOptions().then((res) => { |
| | | // ä¾åºåè¿æ»¤åºisWhite=0 çæ°æ® |
| | | supplierList.value = res.data.filter((item) => item.isWhite == 0); |
| | | }); |
| | | form.value.recorderId = userStore.id; |
| | | form.value.entryDate = getCurrentDate(); |
| | | if (type === "edit") { |
| | | currentId.value = row.id; |
| | | getPurchaseById({ id: row.id, type: 2 }).then((res) => { |
| | | form.value = { ...res }; |
| | | productData.value = form.value.productData; |
| | | if (form.value.salesLedgerFiles) { |
| | | fileList.value = form.value.salesLedgerFiles; |
| | | } else { |
| | | fileList.value = []; |
| | | } |
| | | }); |
| | | } |
| | | dialogFormVisible.value = true; |
| | | }; |
| | | // ä¸ä¼ åæ ¡æ£ |
| | | function handleBeforeUpload(file) { |
| | | // æ ¡æ£æä»¶å¤§å° |
| | | if (file.size > 1024 * 1024 * 10) { |
| | | proxy.$modal.msgError("ä¸ä¼ æä»¶å¤§å°ä¸è½è¶
è¿10MB!"); |
| | | return false; |
| | | } |
| | | proxy.$modal.loading("æ£å¨ä¸ä¼ æä»¶ï¼è¯·ç¨å..."); |
| | | return true; |
| | | } |
| | | // ä¸ä¼ 失败 |
| | | function handleUploadError(err) { |
| | | proxy.$modal.msgError("ä¸ä¼ æä»¶å¤±è´¥"); |
| | | proxy.$modal.closeLoading(); |
| | | } |
| | | // ä¸ä¼ æååè° |
| | | function handleUploadSuccess(res, file, uploadFiles) { |
| | | proxy.$modal.closeLoading(); |
| | | if (res.code === 200) { |
| | | file.tempId = res.data.tempId; |
| | | proxy.$modal.msgSuccess("ä¸ä¼ æå"); |
| | | } else { |
| | | proxy.$modal.msgError(res.msg); |
| | | proxy.$refs.fileUpload.handleRemove(file); |
| | | } |
| | | } |
| | | // ç§»é¤æä»¶ |
| | | function handleRemove(file) { |
| | | console.log("handleRemove", file.id); |
| | | if (file.size > 1024 * 1024 * 10) { |
| | | // ä»
å端æ¸
çï¼ä¸è°ç¨å 餿¥å£åæç¤º |
| | | return; |
| | | } |
| | | if (operationType.value === "edit") { |
| | | let ids = []; |
| | | ids.push(file.id); |
| | | delLedgerFile(ids).then((res) => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | }); |
| | | } |
| | | } |
| | | // æäº¤è¡¨å |
| | | const submitForm = (n) => { |
| | | proxy.$refs["formRef"].validate((valid) => { |
| | | if (valid) { |
| | | if (productData.value.length > 0) { |
| | | form.value.productData = proxy.HaveJson(productData.value); |
| | | } else { |
| | | proxy.$modal.msgWarning("请添å 产åä¿¡æ¯"); |
| | | return; |
| | | } |
| | | let tempFileIds = []; |
| | | if (fileList.value.length > 0) { |
| | | tempFileIds = fileList.value.map((item) => item.tempId); |
| | | } |
| | | form.value.tempFileIds = tempFileIds; |
| | | form.value.type = 2; |
| | | form.value.approvalStatus = n; |
| | | addOrEditPurchase(form.value).then((res) => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | getList(); |
| | | }); |
| | | } |
| | | }); |
| | | }; |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | | dialogFormVisible.value = false; |
| | | }; |
| | | // æå¼äº§åå¼¹æ¡ |
| | | const openProductForm = (type, row, index) => { |
| | | productOperationType.value = type; |
| | | productOperationIndex.value = index; |
| | | productForm.value = {}; |
| | | proxy.resetForm("productFormRef"); |
| | | if (type === "edit") { |
| | | productForm.value = { ...row }; |
| | | } |
| | | productFormVisible.value = true; |
| | | getProductOptions(); |
| | | }; |
| | | const getProductOptions = () => { |
| | | productTreeList().then((res) => { |
| | | productOptions.value = convertIdToValue(res); |
| | | }); |
| | | }; |
| | | const getModels = (value) => { |
| | | if (value) { |
| | | productForm.value.productCategory = findNodeById(productOptions.value, value) || ""; |
| | | modelList({ id: value }).then((res) => { |
| | | modelOptions.value = res; |
| | | }); |
| | | } else { |
| | | productForm.value.productCategory = ""; |
| | | modelOptions.value = []; |
| | | } |
| | | }; |
| | | const getProductModel = (value) => { |
| | | const index = modelOptions.value.findIndex((item) => item.id === value); |
| | | if (index !== -1) { |
| | | productForm.value.specificationModel = modelOptions.value[index].model; |
| | | productForm.value.unit = modelOptions.value[index].unit; |
| | | } else { |
| | | productForm.value.specificationModel = null; |
| | | productForm.value.unit = null; |
| | | } |
| | | }; |
| | | const findNodeById = (nodes, productId) => { |
| | | for (let i = 0; i < nodes.length; i++) { |
| | | if (nodes[i].value === productId) { |
| | | return nodes[i].label; // æ¾å°èç¹ï¼è¿å该èç¹çlabel |
| | | } |
| | | if (nodes[i].children && nodes[i].children.length > 0) { |
| | | const foundNode = findNodeById(nodes[i].children, productId); |
| | | if (foundNode) { |
| | | return foundNode; // å¨åèç¹ä¸æ¾å°ï¼ç´æ¥è¿åï¼å·²ç»æ¯labelåç¬¦ä¸²ï¼ |
| | | } |
| | | } |
| | | } |
| | | return null; // æ²¡ææ¾å°èç¹ï¼è¿ånull |
| | | }; |
| | | function convertIdToValue(data) { |
| | | return data.map((item) => { |
| | | const { id, children, ...rest } = item; |
| | | const newItem = { |
| | | ...rest, |
| | | value: id, // å° id æ¹ä¸º value |
| | | }; |
| | | if (children && children.length > 0) { |
| | | newItem.children = convertIdToValue(children); |
| | | } |
| | | |
| | | return newItem; |
| | | }); |
| | | } |
| | | // æäº¤äº§å表å |
| | | const submitProduct = () => { |
| | | proxy.$refs["productFormRef"].validate((valid) => { |
| | | if (valid) { |
| | | if (operationType.value === "edit") { |
| | | submitProductEdit(); |
| | | } else { |
| | | if (productOperationType.value === "add") { |
| | | productData.value.push({ ...productForm.value }); |
| | | console.log("productData.value---", productData.value); |
| | | } else { |
| | | productData.value[productOperationIndex.value] = { |
| | | ...productForm.value, |
| | | }; |
| | | } |
| | | closeProductDia(); |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | const submitProductEdit = () => { |
| | | productForm.value.salesLedgerId = currentId.value; |
| | | productForm.value.type = 2; |
| | | addOrUpdateSalesLedgerProduct(productForm.value).then((res) => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeProductDia(); |
| | | getPurchaseById({ id: currentId.value, type: 2 }).then((res) => { |
| | | productData.value = res.productData; |
| | | }); |
| | | }); |
| | | }; |
| | | // å é¤äº§å |
| | | const deleteProduct = () => { |
| | | if (productSelectedRows.value.length === 0) { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | if (operationType.value === "add") { |
| | | productSelectedRows.value.forEach((selectedRow) => { |
| | | const index = productData.value.findIndex( |
| | | (product) => product.id === selectedRow.id |
| | | ); |
| | | if (index !== -1) { |
| | | productData.value.splice(index, 1); |
| | | } |
| | | }); |
| | | } else { |
| | | let ids = []; |
| | | if (productSelectedRows.value.length > 0) { |
| | | ids = productSelectedRows.value.map((item) => item.id); |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | delProduct(ids).then((res) => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | closeProductDia(); |
| | | getSalesLedgerWithProducts({ id: currentId.value, type: 2 }).then( |
| | | (res) => { |
| | | productData.value = res.productData; |
| | | } |
| | | ); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | } |
| | | }; |
| | | // å
³é产åå¼¹æ¡ |
| | | const closeProductDia = () => { |
| | | proxy.resetForm("productFormRef"); |
| | | productFormVisible.value = false; |
| | | }; |
| | | // 审æ¹éè¿æ¹æ³ |
| | | const approvePurchase = (row) => { |
| | | ElMessageBox.confirm(`确认éè¿éè´ååå·ä¸º ${row.purchaseContractNumber} ç审æ¹ï¼`, '审æ¹ç¡®è®¤', { |
| | | confirmButtonText: '确认', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning', |
| | | }).then(() => { |
| | | updateApprovalStatus({ id: row.id, approvalStatus: 1}).then((res)=>{ |
| | | proxy.$modal.msgSuccess('å®¡æ¹æå'); |
| | | getList(); |
| | | }) |
| | | }).catch(() => { |
| | | proxy.$modal.msg('已忶审æ¹'); |
| | | }); |
| | | }; |
| | | |
| | | // å®¡æ¹æç»æ¹æ³ |
| | | const rejectPurchase = (row) => { |
| | | ElMessageBox.confirm(`确认æç»éè´ååå·ä¸º ${row.purchaseContractNumber} ç审æ¹ï¼`, '审æ¹ç¡®è®¤', { |
| | | confirmButtonText: '确认', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning', |
| | | }).then(() => { |
| | | updateApprovalStatus({ id: row.id, approvalStatus: 2}).then((res)=>{ |
| | | proxy.$modal.msgSuccess('å®¡æ¹æå'); |
| | | getList(); |
| | | }) |
| | | }).catch(() => { |
| | | proxy.$modal.msg('已忶审æ¹'); |
| | | }); |
| | | }; |
| | | |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/purchase/ledger/export", {}, "éè´å°è´¦.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | // æ£æ¥æ¯å¦æä»äººç»´æ¤çæ°æ® |
| | | const unauthorizedData = selectedRows.value.filter(item => item.recorderName !== userStore.nickName); |
| | | if (unauthorizedData.length > 0) { |
| | | proxy.$modal.msgWarning("ä¸å¯å é¤ä»äººç»´æ¤çæ°æ®"); |
| | | return; |
| | | } |
| | | ids = selectedRows.value.map((item) => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | delPurchase(ids).then((res) => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | const mathNum = () => { |
| | | if (!productForm.value.taxRate) { |
| | | proxy.$modal.msgWarning("请å
éæ©ç¨ç"); |
| | | return; |
| | | } |
| | | if (!productForm.value.taxInclusiveUnitPrice) { |
| | | return; |
| | | } |
| | | if (!productForm.value.quantity) { |
| | | return; |
| | | } |
| | | // å«ç¨æ»ä»·è®¡ç® |
| | | productForm.value.taxInclusiveTotalPrice = |
| | | proxy.calculateTaxIncludeTotalPrice( |
| | | productForm.value.taxInclusiveUnitPrice, |
| | | productForm.value.quantity |
| | | ); |
| | | if (productForm.value.taxRate) { |
| | | // ä¸å«ç¨æ»ä»·è®¡ç® |
| | | productForm.value.taxExclusiveTotalPrice = |
| | | proxy.calculateTaxExclusiveTotalPrice( |
| | | productForm.value.taxInclusiveTotalPrice, |
| | | productForm.value.taxRate |
| | | ); |
| | | } |
| | | }; |
| | | const reverseMathNum = (field) => { |
| | | if (!productForm.value.taxRate) { |
| | | proxy.$modal.msgWarning("请å
éæ©ç¨ç"); |
| | | return; |
| | | } |
| | | const taxRate = Number(productForm.value.taxRate); |
| | | if (!taxRate) return; |
| | | if (field === 'taxInclusiveTotalPrice') { |
| | | // å·²ç¥å«ç¨æ»ä»·åæ°éï¼åç®å«ç¨åä»· |
| | | if (productForm.value.quantity) { |
| | | productForm.value.taxInclusiveUnitPrice = |
| | | (Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.quantity)).toFixed(2); |
| | | } |
| | | // å·²ç¥å«ç¨æ»ä»·åå«ç¨åä»·ï¼åç®æ°é |
| | | else if (productForm.value.taxInclusiveUnitPrice) { |
| | | productForm.value.quantity = |
| | | (Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.taxInclusiveUnitPrice)).toFixed(2); |
| | | } |
| | | // åç®ä¸å«ç¨æ»ä»· |
| | | productForm.value.taxExclusiveTotalPrice = |
| | | (Number(productForm.value.taxInclusiveTotalPrice) / (1 + taxRate / 100)).toFixed(2); |
| | | } else if (field === 'taxExclusiveTotalPrice') { |
| | | // åç®å«ç¨æ»ä»· |
| | | productForm.value.taxInclusiveTotalPrice = |
| | | (Number(productForm.value.taxExclusiveTotalPrice) * (1 + taxRate / 100)).toFixed(2); |
| | | // å·²ç¥æ°éï¼åç®å«ç¨åä»· |
| | | if (productForm.value.quantity) { |
| | | productForm.value.taxInclusiveUnitPrice = |
| | | (Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.quantity)).toFixed(2); |
| | | } |
| | | // å·²ç¥å«ç¨åä»·ï¼åç®æ°é |
| | | else if (productForm.value.taxInclusiveUnitPrice) { |
| | | productForm.value.quantity = |
| | | (Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.taxInclusiveUnitPrice)).toFixed(2); |
| | | } |
| | | } |
| | | }; |
| | | // éå®ååéæ©æ¹åæ¹æ³ |
| | | const salesLedgerChange = async (row) => { |
| | | console.log("row", row); |
| | | var index = salesContractList.value.findIndex((item) => item.id == row); |
| | | console.log("index", index); |
| | | if (index > -1) { |
| | | form.value.projectName = salesContractList.value[index].projectName; |
| | | await querygProductInfoByContractNo(); |
| | | } |
| | | }; |
| | | |
| | | const querygProductInfoByContractNo = async () => { |
| | | const { code, data } = await getProductInfoByContractNo({ |
| | | contractNo: form.value.salesLedgerId, |
| | | }); |
| | | if (code == 200) { |
| | | productData.value = data; |
| | | } |
| | | }; |
| | | |
| | | // æ¾ç¤ºäºç»´ç |
| | | const showQRCode = async (row) => { |
| | | try { |
| | | // æå»ºäºç»´ç å
容ï¼åªå
å«éè´ååå·ï¼çº¯ææ¬ï¼ |
| | | const qrContent = row.purchaseContractNumber || ''; |
| | | // æ£æ¥å
容æ¯å¦ä¸ºç©º |
| | | if (!qrContent || qrContent.trim() === '') { |
| | | proxy.$modal.msgWarning("è¯¥è¡æ²¡æéè´ååå·ï¼æ æ³çæäºç»´ç "); |
| | | return; |
| | | } |
| | | qrCodeUrl.value = await QRCode.toDataURL(qrContent, { |
| | | width: 200, |
| | | margin: 2, |
| | | color: { |
| | | dark: '#000000', |
| | | light: '#FFFFFF' |
| | | } |
| | | }); |
| | | qrCodeDialogVisible.value = true; |
| | | } catch (error) { |
| | | console.error('çæäºç»´ç 失败:', error); |
| | | proxy.$modal.msgError("çæäºç»´ç 失败ï¼" + error.message); |
| | | } |
| | | }; |
| | | |
| | | // ä¸è½½äºç»´ç |
| | | const downloadQRCode = () => { |
| | | if (!qrCodeUrl.value) { |
| | | proxy.$modal.msgWarning("äºç»´ç æªçæ"); |
| | | return; |
| | | } |
| | | |
| | | const a = document.createElement('a'); |
| | | a.href = qrCodeUrl.value; |
| | | a.download = `éè´ååå·äºç»´ç _${new Date().getTime()}.png`; |
| | | document.body.appendChild(a); |
| | | a.click(); |
| | | document.body.removeChild(a); |
| | | proxy.$modal.msgSuccess("ä¸è½½æå"); |
| | | }; |
| | | |
| | | // æ«ç æ°å¢å¯¹è¯æ¡ç¸å
³åé |
| | | const scanAddDialogVisible = ref(false); |
| | | const scanAddForm = reactive({ |
| | | scanContent: "", |
| | | purchaseContractNumber: "", |
| | | supplierName: "", |
| | | projectName: "", |
| | | contractAmount: "", |
| | | paymentMethod: "", |
| | | recorderName: "", |
| | | scanRemark: "", |
| | | }); |
| | | const scanAddRules = { |
| | | purchaseContractNumber: [{ required: true, message: "请è¾å
¥éè´ååå·", trigger: "blur" }], |
| | | supplierName: [{ required: true, message: "请è¾å
¥ä¾åºååç§°", trigger: "blur" }], |
| | | projectName: [{ required: true, message: "请è¾å
¥é¡¹ç®åç§°", trigger: "blur" }], |
| | | }; |
| | | |
| | | // æ«ç ç»è®°å¯¹è¯æ¡ç¸å
³åé |
| | | const scanDialogVisible = ref(false); |
| | | const scanForm = reactive({ |
| | | purchaseContractNumber: "", |
| | | supplierName: "", |
| | | projectName: "", |
| | | scanTime: "", |
| | | scannerName: "", |
| | | scanStatus: "æªæ«ç ", |
| | | scanRemark: "", |
| | | }); |
| | | const scanRules = { |
| | | scanRemark: [{ required: true, message: "请è¾å
¥æ«ç 夿³¨", trigger: "blur" }], |
| | | }; |
| | | const scanRecords = ref([]); |
| | | |
| | | // æå¼æ«ç æ°å¢å¯¹è¯æ¡ |
| | | const openScanAddDialog = () => { |
| | | scanAddForm.scanContent = ""; |
| | | scanAddForm.purchaseContractNumber = ""; |
| | | scanAddForm.supplierName = ""; |
| | | scanAddForm.projectName = ""; |
| | | scanAddForm.contractAmount = ""; |
| | | scanAddForm.paymentMethod = ""; |
| | | scanAddForm.recorderName = userStore.nickName; |
| | | scanAddForm.scanRemark = ""; |
| | | scanAddDialogVisible.value = true; |
| | | }; |
| | | |
| | | // è§£ææ«ç å
å®¹ï¼æ¨¡æè§£æäºç»´ç æ°æ®ï¼ |
| | | const parseScanContent = (content) => { |
| | | if (!content) return; |
| | | |
| | | // 模æè§£æäºç»´ç å
容ï¼è¿éå¯ä»¥æ ¹æ®å®é
éæ±è°æ´è§£æé»è¾ |
| | | // å设æ«ç å
å®¹æ ¼å¼ä¸ºï¼ååå·|ä¾åºå|项ç®|éé¢|仿¬¾æ¹å¼ |
| | | const parts = content.split('|'); |
| | | if (parts.length >= 3) { |
| | | scanAddForm.purchaseContractNumber = parts[0] || ""; |
| | | scanAddForm.supplierName = parts[1] || ""; |
| | | scanAddForm.projectName = parts[2] || ""; |
| | | scanAddForm.contractAmount = parts[3] || ""; |
| | | scanAddForm.paymentMethod = parts[4] || ""; |
| | | } |
| | | }; |
| | | |
| | | // å
³éæ«ç æ°å¢å¯¹è¯æ¡ |
| | | const closeScanAddDialog = () => { |
| | | scanAddDialogVisible.value = false; |
| | | proxy.resetForm("scanAddFormRef"); |
| | | }; |
| | | |
| | | // æäº¤æ«ç æ°å¢ |
| | | const submitScanAdd = () => { |
| | | proxy.$refs["scanAddFormRef"].validate((valid) => { |
| | | if (valid) { |
| | | // æå»ºæ°å¢æ°æ® |
| | | const newData = { |
| | | purchaseContractNumber: scanAddForm.purchaseContractNumber, |
| | | supplierName: scanAddForm.supplierName, |
| | | projectName: scanAddForm.projectName, |
| | | contractAmount: scanAddForm.contractAmount, |
| | | paymentMethod: scanAddForm.paymentMethod, |
| | | recorderName: scanAddForm.recorderName, |
| | | entryDate: getCurrentDate(), |
| | | remark: scanAddForm.scanRemark, |
| | | type: 2 |
| | | }; |
| | | |
| | | // æ¨¡ææ°å¢æå |
| | | proxy.$modal.msgSuccess("æ«ç æ°å¢æåï¼"); |
| | | closeScanAddDialog(); |
| | | |
| | | // å¯ä»¥éæ©æ¯å¦å·æ°å表 |
| | | // getList(); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // æå¼æ«ç ç»è®°å¯¹è¯æ¡ |
| | | const openScanDialog = (row) => { |
| | | scanForm.purchaseContractNumber = row.purchaseContractNumber; |
| | | scanForm.supplierName = row.supplierName; |
| | | scanForm.projectName = row.projectName; |
| | | scanForm.scanTime = getCurrentDateTime(); |
| | | scanForm.scannerName = userStore.nickName; |
| | | scanForm.scanStatus = "æªæ«ç "; |
| | | scanForm.scanRemark = ""; |
| | | scanRecords.value = []; |
| | | scanDialogVisible.value = true; |
| | | }; |
| | | |
| | | // å
³éæ«ç ç»è®°å¯¹è¯æ¡ |
| | | const closeScanDialog = () => { |
| | | scanDialogVisible.value = false; |
| | | proxy.resetForm("scanFormRef"); |
| | | }; |
| | | |
| | | // æäº¤æ«ç ç»è®° |
| | | const submitScan = () => { |
| | | proxy.$refs["scanFormRef"].validate((valid) => { |
| | | if (valid) { |
| | | // æ·»å æ«ç è®°å½ |
| | | scanRecords.value.push({ |
| | | ...scanForm, |
| | | id: Date.now(), // 模æID |
| | | scanTime: getCurrentDateTime(), |
| | | }); |
| | | scanForm.scanStatus = "å·²æ«ç "; |
| | | scanForm.scanRemark = scanForm.scanRemark || "æ "; |
| | | proxy.$modal.msgSuccess("æ«ç ç»è®°æåï¼"); |
| | | closeScanDialog(); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // è·åå½åæ¥ææ¶é´ |
| | | function getCurrentDateTime() { |
| | | const now = new Date(); |
| | | const year = now.getFullYear(); |
| | | const month = String(now.getMonth() + 1).padStart(2, "0"); |
| | | const day = String(now.getDate()).padStart(2, "0"); |
| | | const hours = String(now.getHours()).padStart(2, "0"); |
| | | const minutes = String(now.getMinutes()).padStart(2, "0"); |
| | | const seconds = String(now.getSeconds()).padStart(2, "0"); |
| | | return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; |
| | | } |
| | | |
| | | // æ·»å è¡ç±»åæ¹æ³ |
| | | const tableRowClassName = ({ row }) => { |
| | | return row.isInvalid ? 'invalid-row' : ''; |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .invalid-row { |
| | | opacity: 0.6; |
| | | background-color: #f5f7fa; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="report-generation"> |
| | | <!-- 页é¢å¤´é¨ --> |
| | | <div class="page-header"> |
| | | <h2>é¡¹ç®æ»ç»æ¥åçæ</h2> |
| | | <div class="header-actions"> |
| | | <el-button v-if="!reportGenerated" type="primary" @click="generateReport" :loading="generating"> |
| | | <el-icon><Document /></el-icon> |
| | | çææ¥å |
| | | </el-button> |
| | | <el-button v-if="reportGenerated" type="primary" @click="resetConfig"> |
| | | <el-icon><Refresh /></el-icon> |
| | | çææ°æ¥å |
| | | </el-button> |
| | | <el-button @click="exportReport" :disabled="!reportGenerated"> |
| | | <el-icon><Download /></el-icon> |
| | | å¯¼åºæ¥å |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- æ¥åé
ç½®åºå --> |
| | | <el-card class="config-card" v-if="!reportGenerated"> |
| | | <template #header> |
| | | <span>æ¥åé
ç½®</span> |
| | | </template> |
| | | <el-form :model="reportConfig" label-width="120px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="项ç®åç§°"> |
| | | <el-select v-model="reportConfig.projectId" placeholder="è¯·éæ©é¡¹ç®"> |
| | | <el-option |
| | | v-for="project in projectList" |
| | | :key="project.id" |
| | | :label="project.name" |
| | | :value="project.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¥å卿"> |
| | | <el-date-picker |
| | | v-model="reportConfig.dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | </el-card> |
| | | |
| | | <!-- æ¥åå
容å±ç¤ºåºå --> |
| | | <div v-if="reportGenerated" class="report-content"> |
| | | <!-- 项ç®åºæ¬ä¿¡æ¯ --> |
| | | <el-card class="report-section"> |
| | | <template #header> |
| | | <span>项ç®åºæ¬ä¿¡æ¯</span> |
| | | </template> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions-item label="项ç®åç§°">{{ reportData.projectInfo.name }}</el-descriptions-item> |
| | | <el-descriptions-item label="项ç®ç»ç">{{ reportData.projectInfo.manager }}</el-descriptions-item> |
| | | <el-descriptions-item label="å¼å§æ¶é´">{{ reportData.projectInfo.startDate }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç»ææ¶é´">{{ reportData.projectInfo.endDate }}</el-descriptions-item> |
| | | <el-descriptions-item label="项ç®ç¶æ"> |
| | | <el-tag :type="getStatusType(reportData.projectInfo.status)"> |
| | | {{ reportData.projectInfo.status }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="æ»é¢ç®">{{ reportData.projectInfo.budget }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | </el-card> |
| | | |
| | | <!-- ä»»å¡å®æçç»è®¡ --> |
| | | <el-card class="report-section"> |
| | | <template #header> |
| | | <span>ä»»å¡å®æçç»è®¡</span> |
| | | </template> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div class="completion-stats"> |
| | | <div class="stat-item"> |
| | | <div class="stat-label">æ»ä»»å¡æ°</div> |
| | | <div class="stat-value">{{ reportData.taskStats.total }}</div> |
| | | </div> |
| | | <div class="stat-item"> |
| | | <div class="stat-label">已宿</div> |
| | | <div class="stat-value completed">{{ reportData.taskStats.completed }}</div> |
| | | </div> |
| | | <div class="stat-item"> |
| | | <div class="stat-label">è¿è¡ä¸</div> |
| | | <div class="stat-value in-progress">{{ reportData.taskStats.inProgress }}</div> |
| | | </div> |
| | | <div class="stat-item"> |
| | | <div class="stat-label">æªå¼å§</div> |
| | | <div class="stat-value pending">{{ reportData.taskStats.pending }}</div> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="completion-rate"> |
| | | <el-progress |
| | | type="circle" |
| | | :percentage="reportData.taskStats.completionRate" |
| | | :width="150" |
| | | :stroke-width="8" |
| | | > |
| | | <template #default="{ percentage }"> |
| | | <span class="percentage-value">{{ percentage }}%</span> |
| | | <div class="percentage-label">宿ç</div> |
| | | </template> |
| | | </el-progress> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | </el-card> |
| | | |
| | | <!-- é®é¢è®°å½ç»è®¡ --> |
| | | <el-card class="report-section"> |
| | | <template #header> |
| | | <span>é®é¢è®°å½ç»è®¡</span> |
| | | </template> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="8"> |
| | | <div class="issue-summary"> |
| | | <div class="summary-item"> |
| | | <div class="summary-label">æ»é®é¢æ°</div> |
| | | <div class="summary-value">{{ reportData.issueStats.total }}</div> |
| | | </div> |
| | | <div class="summary-item"> |
| | | <div class="summary-label">已解å³</div> |
| | | <div class="summary-value resolved">{{ reportData.issueStats.resolved }}</div> |
| | | </div> |
| | | <div class="summary-item"> |
| | | <div class="summary-label">å¾
è§£å³</div> |
| | | <div class="summary-value pending">{{ reportData.issueStats.pending }}</div> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="16"> |
| | | <el-table :data="reportData.issueStats.topIssues" size="small"> |
| | | <el-table-column prop="title" label="主è¦é®é¢" /> |
| | | <el-table-column prop="severity" label="严éç¨åº¦" width="100"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="getSeverityType(row.severity)" size="small"> |
| | | {{ row.severity }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="status" label="ç¶æ" width="80"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="row.status === '已解å³' ? 'success' : 'warning'" size="small"> |
| | | {{ row.status }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-col> |
| | | </el-row> |
| | | </el-card> |
| | | |
| | | <!-- å»¶è¯¯åæ --> |
| | | <el-card class="report-section"> |
| | | <template #header> |
| | | <span>延误åæ</span> |
| | | </template> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div class="delay-stats"> |
| | | <div class="delay-item"> |
| | | <div class="delay-label">å»¶è¯¯ä»»å¡æ°</div> |
| | | <div class="delay-value">{{ reportData.delayAnalysis.delayedTasks }}</div> |
| | | </div> |
| | | <div class="delay-item"> |
| | | <div class="delay-label">å¹³å延误天æ°</div> |
| | | <div class="delay-value">{{ reportData.delayAnalysis.avgDelayDays }}</div> |
| | | </div> |
| | | <div class="delay-item"> |
| | | <div class="delay-label">æå¤§å»¶è¯¯å¤©æ°</div> |
| | | <div class="delay-value">{{ reportData.delayAnalysis.maxDelayDays }}</div> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="delay-reasons"> |
| | | <h4>主è¦å»¶è¯¯åå </h4> |
| | | <ul> |
| | | <li v-for="reason in reportData.delayAnalysis.reasons" :key="reason.reason"> |
| | | {{ reason.reason }} ({{ reason.count }}次) |
| | | </li> |
| | | </ul> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | </el-card> |
| | | |
| | | <!-- å¢é绩æ --> |
| | | <el-card class="report-section"> |
| | | <template #header> |
| | | <span>å¢é绩æ</span> |
| | | </template> |
| | | <el-table :data="reportData.teamPerformance" size="small"> |
| | | <el-table-column prop="name" label="æåå§å" /> |
| | | <el-table-column prop="completedTasks" label="宿任塿°" /> |
| | | <el-table-column prop="completionRate" label="宿ç" width="100"> |
| | | <template #default="{ row }"> |
| | | <el-progress :percentage="row.completionRate" :show-text="false" /> |
| | | <span style="margin-left: 10px;">{{ row.completionRate }}%</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="performance" label="绩æè¯çº§" width="100"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="getPerformanceType(row.performance)"> |
| | | {{ row.performance }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | |
| | | <!-- æ»ç»ä¸å»ºè®® --> |
| | | <el-card class="report-section"> |
| | | <template #header> |
| | | <span>æ»ç»ä¸å»ºè®®</span> |
| | | </template> |
| | | <div class="summary-content"> |
| | | <h4>é¡¹ç®æ»ç»</h4> |
| | | <p>{{ reportData.summary.conclusion }}</p> |
| | | |
| | | <h4>æ¹è¿å»ºè®®</h4> |
| | | <ul> |
| | | <li v-for="suggestion in reportData.summary.suggestions" :key="suggestion"> |
| | | {{ suggestion }} |
| | | </li> |
| | | </ul> |
| | | </div> |
| | | </el-card> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from 'vue' |
| | | import { Document, Download, Refresh } from '@element-plus/icons-vue' |
| | | import { ElMessage } from 'element-plus' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const generating = ref(false) |
| | | const reportGenerated = ref(false) |
| | | |
| | | // æ¥åé
ç½® |
| | | const reportConfig = reactive({ |
| | | projectId: '', |
| | | dateRange: [] |
| | | }) |
| | | |
| | | // 项ç®å表 |
| | | const projectList = ref([ |
| | | { id: 1, name: '产ååºå管çç³»ç»' }, |
| | | { id: 2, name: '客æ·å
³ç³»ç®¡çå¹³å°' }, |
| | | { id: 3, name: 'è´¢å¡ç®¡çç³»ç»å级' }, |
| | | { id: 4, name: 'ç§»å¨ç«¯åºç¨å¼å' } |
| | | ]) |
| | | |
| | | // æ¥åæ°æ® |
| | | const reportData = ref({}) |
| | | |
| | | // æ¨¡ææ¥åæ°æ® |
| | | const mockReportData = { |
| | | projectInfo: { |
| | | name: '产ååºå管çç³»ç»', |
| | | manager: 'å¼ ä¸', |
| | | startDate: '2024-01-01', |
| | | endDate: '2024-03-31', |
| | | status: '已宿', |
| | | budget: 'Â¥500,000' |
| | | }, |
| | | taskStats: { |
| | | total: 45, |
| | | completed: 38, |
| | | inProgress: 5, |
| | | pending: 2, |
| | | completionRate: 84 |
| | | }, |
| | | issueStats: { |
| | | total: 12, |
| | | resolved: 9, |
| | | pending: 3, |
| | | topIssues: [ |
| | | { title: 'æ°æ®åºè¿æ¥è¶
æ¶', severity: 'é«', status: '已解å³' }, |
| | | { title: 'å端页é¢ååºæ
¢', severity: 'ä¸', status: '已解å³' }, |
| | | { title: 'æééªè¯å¼å¸¸', severity: 'é«', status: 'å¾
è§£å³' }, |
| | | { title: 'æ¥è¡¨å¯¼åºåè½ç¼ºå¤±', severity: 'ä¸', status: 'å¾
è§£å³' } |
| | | ] |
| | | }, |
| | | delayAnalysis: { |
| | | delayedTasks: 8, |
| | | avgDelayDays: 3.5, |
| | | maxDelayDays: 12, |
| | | reasons: [ |
| | | { reason: '鿱忴', count: 3 }, |
| | | { reason: 'ææ¯é¾é¢', count: 2 }, |
| | | { reason: 'èµæºä¸è¶³', count: 2 }, |
| | | { reason: 'å¤é¨ä¾èµ', count: 1 } |
| | | ] |
| | | }, |
| | | teamPerformance: [ |
| | | { name: 'æå', completedTasks: 12, completionRate: 92, performance: 'ä¼ç§' }, |
| | | { name: 'çäº', completedTasks: 10, completionRate: 85, performance: 'è¯å¥½' }, |
| | | { name: 'èµµå
', completedTasks: 8, completionRate: 78, performance: 'è¯å¥½' }, |
| | | { name: 'é±ä¸', completedTasks: 8, completionRate: 72, performance: 'ä¸è¬' } |
| | | ], |
| | | summary: { |
| | | conclusion: 'æ¬é¡¹ç®æ´ä½æ§è¡æ
åµè¯å¥½ï¼ä»»å¡å®æçè¾¾å°84%ï¼å¢éå使çè¾é«ã主è¦é®é¢éä¸å¨ææ¯å®ç°å鿱忴æ¹é¢ï¼éè¿åæ¶æ²éåææ¯æ»å
³ï¼å¤§é¨åé®é¢å·²å¾å°è§£å³ã', |
| | | suggestions: [ |
| | | 'å å¼ºéæ±åæé¶æ®µçå·¥ä½ï¼åå°åæéæ±åæ´', |
| | | 'å»ºç«ææ¯é¾é¢é¢è¦æºå¶ï¼æåè¯å«åè§£å³ææ¯é£é©', |
| | | 'ä¼åå¢éèµæºé
ç½®ï¼æé«æ´ä½å·¥ä½æç', |
| | | 'å®å项ç®ç®¡çæµç¨ï¼å 强è¿ç¨çæ§' |
| | | ] |
| | | } |
| | | } |
| | | |
| | | // çææ¥å |
| | | const generateReport = async () => { |
| | | if (!reportConfig.projectId) { |
| | | ElMessage.warning('è¯·éæ©é¡¹ç®') |
| | | return |
| | | } |
| | | |
| | | generating.value = true |
| | | |
| | | // 模æçææ¥åçè¿ç¨ |
| | | setTimeout(() => { |
| | | reportData.value = mockReportData |
| | | reportGenerated.value = true |
| | | generating.value = false |
| | | ElMessage.success('æ¥åçææå') |
| | | }, 2000) |
| | | } |
| | | |
| | | // å¯¼åºæ¥å |
| | | const exportReport = () => { |
| | | ElMessage.success('æ¥å导åºåè½å¼åä¸...') |
| | | } |
| | | |
| | | // éç½®é
ç½®ï¼çææ°æ¥å |
| | | const resetConfig = () => { |
| | | reportGenerated.value = false |
| | | reportData.value = {} |
| | | // éç½®é
置为é»è®¤å¼ |
| | | reportConfig.projectId = 1 |
| | | const endDate = new Date() |
| | | const startDate = new Date() |
| | | startDate.setDate(startDate.getDate() - 30) |
| | | reportConfig.dateRange = [ |
| | | startDate.toISOString().split('T')[0], |
| | | endDate.toISOString().split('T')[0] |
| | | ] |
| | | ElMessage.success('å·²éç½®é
ç½®ï¼å¯ä»¥çææ°æ¥å') |
| | | } |
| | | |
| | | // è·åç¶æç±»å |
| | | const getStatusType = (status) => { |
| | | const statusMap = { |
| | | '已宿': 'success', |
| | | 'è¿è¡ä¸': 'warning', |
| | | 'æªå¼å§': 'info', |
| | | 'å·²æå': 'danger' |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | |
| | | // è·å严éç¨åº¦ç±»å |
| | | const getSeverityType = (severity) => { |
| | | const severityMap = { |
| | | 'é«': 'danger', |
| | | 'ä¸': 'warning', |
| | | 'ä½': 'success' |
| | | } |
| | | return severityMap[severity] || 'info' |
| | | } |
| | | |
| | | // è·å绩æç±»å |
| | | const getPerformanceType = (performance) => { |
| | | const performanceMap = { |
| | | 'ä¼ç§': 'success', |
| | | 'è¯å¥½': 'primary', |
| | | 'ä¸è¬': 'warning', |
| | | 'å¾
æ¹è¿': 'danger' |
| | | } |
| | | return performanceMap[performance] || 'info' |
| | | } |
| | | |
| | | // ç»ä»¶æè½½æ¶åå§å |
| | | onMounted(() => { |
| | | // 设置é»è®¤é¡¹ç® |
| | | reportConfig.projectId = 1 |
| | | // 设置é»è®¤æ¥æèå´ï¼æè¿30å¤©ï¼ |
| | | const endDate = new Date() |
| | | const startDate = new Date() |
| | | startDate.setDate(startDate.getDate() - 30) |
| | | reportConfig.dateRange = [ |
| | | startDate.toISOString().split('T')[0], |
| | | endDate.toISOString().split('T')[0] |
| | | ] |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .report-generation { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .page-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | margin: 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .header-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .config-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .report-content { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .report-section { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .completion-stats { |
| | | display: flex; |
| | | justify-content: space-around; |
| | | padding: 20px 0; |
| | | } |
| | | |
| | | .stat-item { |
| | | text-align: center; |
| | | } |
| | | |
| | | .stat-label { |
| | | font-size: 14px; |
| | | color: #909399; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .stat-value { |
| | | font-size: 24px; |
| | | font-weight: bold; |
| | | color: #303133; |
| | | } |
| | | |
| | | .stat-value.completed { |
| | | color: #67c23a; |
| | | } |
| | | |
| | | .stat-value.in-progress { |
| | | color: #e6a23c; |
| | | } |
| | | |
| | | .stat-value.pending { |
| | | color: #909399; |
| | | } |
| | | |
| | | .completion-rate { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | height: 200px; |
| | | } |
| | | |
| | | .percentage-value { |
| | | font-size: 20px; |
| | | font-weight: bold; |
| | | color: #409eff; |
| | | } |
| | | |
| | | .percentage-label { |
| | | font-size: 12px; |
| | | color: #909399; |
| | | margin-top: 4px; |
| | | } |
| | | |
| | | .issue-summary { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | padding: 20px 0; |
| | | } |
| | | |
| | | .summary-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .summary-label { |
| | | font-size: 14px; |
| | | color: #606266; |
| | | } |
| | | |
| | | .summary-value { |
| | | font-size: 18px; |
| | | font-weight: bold; |
| | | color: #303133; |
| | | } |
| | | |
| | | .summary-value.resolved { |
| | | color: #67c23a; |
| | | } |
| | | |
| | | .summary-value.pending { |
| | | color: #e6a23c; |
| | | } |
| | | |
| | | .delay-stats { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | padding: 20px 0; |
| | | } |
| | | |
| | | .delay-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .delay-label { |
| | | font-size: 14px; |
| | | color: #606266; |
| | | } |
| | | |
| | | .delay-value { |
| | | font-size: 18px; |
| | | font-weight: bold; |
| | | color: #e6a23c; |
| | | } |
| | | |
| | | .delay-reasons h4 { |
| | | margin: 0 0 15px 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .delay-reasons ul { |
| | | margin: 0; |
| | | padding-left: 20px; |
| | | } |
| | | |
| | | .delay-reasons li { |
| | | margin-bottom: 8px; |
| | | color: #606266; |
| | | } |
| | | |
| | | .summary-content h4 { |
| | | margin: 0 0 10px 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .summary-content p { |
| | | line-height: 1.6; |
| | | color: #606266; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .summary-content ul { |
| | | margin: 0; |
| | | padding-left: 20px; |
| | | } |
| | | |
| | | .summary-content li { |
| | | margin-bottom: 8px; |
| | | color: #606266; |
| | | line-height: 1.5; |
| | | } |
| | | </style> |
| | |
| | | </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> |
| | | </div> |
| | |
| | | |
| | | <script setup> |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { onMounted, ref, reactive, toRefs } from "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: { |
| | |
| | | status: "", |
| | | }, |
| | | form: { |
| | | id: "", |
| | | programName: "", |
| | | status: "stopped", |
| | | description: "", |
| | | createTime: "", |
| | | status: "", |
| | | description: "" |
| | | }, |
| | | dialogVisible: false, |
| | | dialogTitle: "", |
| | | dialogType: "add", |
| | | selectedIds: [], |
| | | tableLoading: false, |
| | | page: { |
| | | current: 1, |
| | | size: 100, |
| | | size: 20, |
| | | total: 0, |
| | | }, |
| | | tableData: [], |
| | |
| | | |
| | | // 表åå¼ç¨ |
| | | const formRef = ref(); |
| | | // éæ©çè¡æ°æ® |
| | | const selectedRows = ref([]); |
| | | |
| | | // 表åéªè¯è§å |
| | | const rules = { |
| | |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 230, |
| | | width: 150, |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | |
| | | openForm("edit", row); |
| | | } |
| | | }, |
| | | { |
| | | name: "å¼å§", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | handleStart(row); |
| | | }, |
| | | disabled: (row) => row.status !== 'stopped' |
| | | }, |
| | | { |
| | | name: "忢", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | handleStop(row); |
| | | }, |
| | | disabled: (row) => row.status === 'stopped' |
| | | } |
| | | // { |
| | | // name: "å¼å§", |
| | | // type: "text", |
| | | // clickFun: (row) => { |
| | | // handleStart(row); |
| | | // }, |
| | | // disabled: (row) => row.status !== 'stopped' |
| | | // }, |
| | | // { |
| | | // name: "忢", |
| | | // type: "text", |
| | | // clickFun: (row) => { |
| | | // handleStop(row); |
| | | // }, |
| | | // disabled: (row) => row.status === 'stopped' |
| | | // } |
| | | ] |
| | | } |
| | | ]); |
| | | |
| | | // æ¨¡ææ°æ® |
| | | const mockData = [ |
| | | { |
| | | id: "1", |
| | | programName: "订åå¤çRPA", |
| | | status: "running", |
| | | description: "èªå¨å¤ç客æ·è®¢åï¼å
æ¬éªè¯ãåé
å确认", |
| | | createTime: "2024-01-15 10:30:00" |
| | | }, |
| | | { |
| | | id: "2", |
| | | programName: "åºå忥RPA", |
| | | status: "stopped", |
| | | description: "忥å¤ä¸ªä»åºçåºåæ°æ®ï¼ç¡®ä¿æ°æ®ä¸è´æ§", |
| | | createTime: "2024-01-14 15:20:00" |
| | | }, |
| | | { |
| | | id: "3", |
| | | programName: "æ¥è¡¨çæRPA", |
| | | status: "error", |
| | | description: "èªå¨çææ¯æ¥é宿¥è¡¨ååºåæ¥è¡¨", |
| | | createTime: "2024-01-13 09:15:00" |
| | | } |
| | | ]; |
| | | |
| | | // çå½å¨æ |
| | | onMounted(() => { |
| | |
| | | |
| | | // æ¥è¯¢æ°æ® |
| | | const handleQuery = () => { |
| | | page.value.current = 1; |
| | | // page.value.current = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | |
| | | // 模æAPIè°ç¨å»¶è¿ |
| | | setTimeout(() => { |
| | | let filteredData = [...mockData]; |
| | | |
| | | // æ ¹æ®æç´¢æ¡ä»¶è¿æ»¤æ°æ® |
| | | if (searchForm.value.programName) { |
| | | filteredData = filteredData.filter(item => |
| | | item.programName.toLowerCase().includes(searchForm.value.programName.toLowerCase()) |
| | | ); |
| | | } |
| | | |
| | | if (searchForm.value.status) { |
| | | filteredData = filteredData.filter(item => item.status === searchForm.value.status); |
| | | } |
| | | |
| | | tableData.value = filteredData; |
| | | page.value.total = filteredData.length; |
| | | listRpa({...page.value, ...searchForm.value}) |
| | | .then(res => { |
| | | tableLoading.value = false; |
| | | }, 500); |
| | | tableData.value = res.data.records |
| | | page.total = res.data.total; |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | }) |
| | | }; |
| | | |
| | | // å页å¤ç |
| | |
| | | |
| | | // éæ©ååå¤ç |
| | | const handleSelectionChange = (selection) => { |
| | | selectedIds.value = selection.map(item => item.id); |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | // æå¼è¡¨å |
| | | const openForm = (type, row) => { |
| | | dialogType.value = type; |
| | | dialogVisible.value = true; |
| | | |
| | | |
| | | if (type === "add") { |
| | | dialogTitle.value = "æ·»å RPA"; |
| | | form.value = { |
| | | id: "", |
| | | programName: "", |
| | | status: "stopped", |
| | | description: "", |
| | | createTime: "", |
| | | }; |
| | | } else { |
| | | dialogTitle.value = "ç¼è¾RPA"; |
| | | form.value = { ...row }; |
| | |
| | | // æäº¤è¡¨å |
| | | const submitForm = async () => { |
| | | if (!formRef.value) return; |
| | | |
| | | |
| | | try { |
| | | await formRef.value.validate(); |
| | | |
| | | |
| | | if (dialogType.value === "add") { |
| | | // æ·»å æ°RPA |
| | | const newRPA = { |
| | | id: Date.now().toString(), |
| | | programName: form.value.programName, |
| | | status: form.value.status, |
| | | description: form.value.description, |
| | | createTime: new Date().toLocaleString(), |
| | | }; |
| | | |
| | | mockData.unshift(newRPA); |
| | | ElMessage.success("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 |
| | | const index = mockData.findIndex(item => item.id === form.value.id); |
| | | if (index !== -1) { |
| | | mockData[index] = { ...form.value }; |
| | | ElMessage.success("RPAæ´æ°æå"); |
| | | } |
| | | updateRpa({...form.value}).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("æ´æ°æå"); |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | } |
| | | |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | } catch (error) { |
| | | console.error("表åéªè¯å¤±è´¥:", error); |
| | | } |
| | |
| | | // å é¤RPA |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedIds.value.length > 0) { |
| | | ids = selectedIds.value.map((item) => item.id); |
| | | } else { |
| | | ElMessage.warning("è¯·éæ©è¦å é¤çRPA"); |
| | | return; |
| | | } |
| | | |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | // 仿¨¡ææ°æ®ä¸å é¤éä¸ç项 |
| | | ids.forEach(id => { |
| | | const index = mockData.findIndex(item => item.id === id); |
| | | if (index !== -1) { |
| | | mockData.splice(index, 1); |
| | | } |
| | | }); |
| | | |
| | | ElMessage.success("å 餿å"); |
| | | selectedIds.value = []; |
| | | getList(); |
| | | }).catch(() => { |
| | | // ç¨æ·åæ¶ |
| | | }); |
| | | 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> |
| | | </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="320" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button link @click="viewRegulation(scope.row)">æ¥ç</el-button> |
| | | <el-button link type="primary" @click="handleEdit(scope.row)">ç¼è¾</el-button> |
| | | <el-button link type="danger" @click="repealEdit(scope.row)">åºå¼</el-button> |
| | | <el-button link type="success" @click="viewVersionHistory(scope.row)">çæ¬åå²</el-button> |
| | | <el-button link type="warning" @click="viewReadStatus(scope.row)">é
è¯»ç¶æ</el-button> |
| | | <el-button link type="primary" @click="openFileDialog(scope.row)">éä»¶</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- ç¨å°ç³è¯·å¯¹è¯æ¡ï¼å·²ç§»é¤ï¼ --> |
| | | |
| | | <!-- è§ç« å¶åº¦åå¸å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="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> |
| | | <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> |
| | | <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> |
| | | <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="hr">人äºé¨é¨</el-checkbox> |
| | | <el-checkbox label="finance">è´¢å¡é¨é¨</el-checkbox> |
| | | <el-checkbox label="tech">ææ¯é¨é¨</el-checkbox> |
| | | </el-checkbox-group> |
| | | </el-form-item> |
| | | <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 @click="showRegulationDialog = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitRegulation">åå¸å¶åº¦</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- ç¨å°è¯¦æ
å¯¹è¯æ¡ï¼å·²ç§»é¤ï¼ --> |
| | | |
| | | <!-- è§ç« å¶åº¦è¯¦æ
å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="showRegulationDetailDialog" title="è§ç« å¶åº¦è¯¦æ
" width="800px"> |
| | | <div v-if="currentRegulationDetail"> |
| | | <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> |
| | | <el-descriptions-item label="çæ¬">{{ currentRegulationDetail.version }}</el-descriptions-item> |
| | | <el-descriptions-item label="åå¸äºº">{{ currentRegulationDetail.createUserName }}</el-descriptions-item> |
| | | <el-descriptions-item label="å叿¶é´">{{ currentRegulationDetail.createTime }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | <div class="mt-20"> |
| | | <h4>å¶åº¦å
容</h4> |
| | | <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> |
| | | </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="åæ´è¯´æ"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'"> |
| | | {{ scope.row.status === 'active' ? 'çæä¸' : 'å·²åºæ¢' }} |
| | | </el-tag> |
| | | </template> |
| | | </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"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'confirmed' ? 'success' : 'warning'"> |
| | | {{ scope.row.status === 'confirmed' ? '已确认' : 'æªç¡®è®¤' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-dialog> |
| | | |
| | | <FileListDialog |
| | | ref="fileListDialogRef" |
| | | v-model="fileDialogVisible" |
| | | :show-upload-button="true" |
| | | :show-delete-button="true" |
| | | :delete-method="handleAttachmentDelete" |
| | | :rules-regulations-management-id="currentFileRuleId" |
| | | :name-column-label="'éä»¶åç§°'" |
| | | @upload="handleAttachmentUpload" |
| | | /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, getCurrentInstance } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { listRuleManagement,addRuleManagement,updateRuleManagement,delRuleManagement,getReadingStatusByRuleId,addReadingStatus,updateReadingStatus } from '@/api/collaborativeApproval/sealManagement.js' |
| | | import FileListDialog from '@/components/Dialog/FileListDialog.vue' |
| | | import { listRuleFiles, delRuleFile, addRuleFile } from '@/api/collaborativeApproval/rulesRegulationsManagementFile.js' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const operationType = ref('add') |
| | | const tableData = ref([]) |
| | | const tableLoading = ref(false) |
| | | // å页忰 |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0 |
| | | }) |
| | | // éä»¶å¼¹çª |
| | | const fileDialogVisible = ref(false) |
| | | const fileListDialogRef = ref(null) |
| | | const currentFileRuleId = ref(null) |
| | | const filePage = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0 |
| | | }) |
| | | // è§ç« å¶åº¦ç¸å
³ |
| | | 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 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 regulations = ref([]) |
| | | |
| | | const versionHistory = 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 getCategoryText = (category) => { |
| | | const categoryMap = { |
| | | hr: '人äºå¶åº¦', |
| | | finance: 'è´¢å¡å¶åº¦', |
| | | safety: 'å®å
¨å¶åº¦', |
| | | tech: 'ææ¯å¶åº¦' |
| | | } |
| | | return categoryMap[category] || 'æªç¥' |
| | | } |
| | | // æç´¢å¶åº¦ |
| | | const searchRegulations = () => { |
| | | page.current=1 |
| | | getRegulationList() |
| | | } |
| | | // éç½®å¶åº¦æç´¢ |
| | | const resetRegulationSearch = () => { |
| | | regulationSearchForm.title = '' |
| | | regulationSearchForm.category = '' |
| | | searchRegulations() |
| | | } |
| | | // æ°å¢ |
| | | 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) |
| | | } |
| | | } |
| | | //éç½®å¶åº¦è¡¨å |
| | | const resetRegulationForm = () => { |
| | | Object.assign(regulationForm, { |
| | | id: '', |
| | | regulationNum: '', |
| | | title: '', |
| | | category: '', |
| | | content: '', |
| | | version: '', |
| | | status: 'active', |
| | | readCount: 0, |
| | | effectiveTime: '', |
| | | scope: [], |
| | | requireConfirm: 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) => { |
| | | 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' |
| | | } |
| | | 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 { proxy } = getCurrentInstance() |
| | | const handleExport = () => { |
| | | proxy.download('/rulesRegulationsManagement/export', { ...regulationSearchForm }, 'è§ç« å¶åº¦.xlsx') |
| | | } |
| | | |
| | | // éä»¶ï¼æ¥è¯¢ |
| | | const fetchRuleFiles = async (rulesRegulationsManagementId) => { |
| | | const params = { |
| | | current: filePage.current, |
| | | size: filePage.size, |
| | | rulesRegulationsManagementId |
| | | } |
| | | const res = await listRuleFiles(params) |
| | | const records = res?.data?.records || [] |
| | | filePage.total = res?.data?.total || records.length |
| | | const mapped = records.map(item => ({ |
| | | id: item.id, |
| | | name: item.fileName || item.name, |
| | | url: item.fileUrl || item.url, |
| | | raw: item |
| | | })) |
| | | fileListDialogRef.value?.setList(mapped) |
| | | } |
| | | |
| | | // æå¼éä»¶å¼¹çª |
| | | const openFileDialog = async (row) => { |
| | | currentFileRuleId.value = row.id |
| | | fileDialogVisible.value = true |
| | | await fetchRuleFiles(row.id) |
| | | } |
| | | |
| | | // å·æ°éä»¶å表 |
| | | const refreshFileList = async () => { |
| | | if (!currentFileRuleId.value) return |
| | | await fetchRuleFiles(currentFileRuleId.value) |
| | | } |
| | | |
| | | // ä¸ä¼ éä»¶ï¼ç±åç»ä»¶è§¦åï¼ |
| | | const handleAttachmentUpload = async (filePayload) => { |
| | | if (!currentFileRuleId.value) return |
| | | const payload = { |
| | | name: filePayload?.fileName || filePayload?.name, |
| | | url: filePayload?.fileUrl || filePayload?.url, |
| | | rulesRegulationsManagementId: currentFileRuleId.value |
| | | } |
| | | await addRuleFile(payload) |
| | | ElMessage.success('æä»¶ä¸ä¼ æå') |
| | | await refreshFileList() |
| | | } |
| | | |
| | | // å é¤éä»¶ |
| | | const handleAttachmentDelete = async (row) => { |
| | | if (!row?.id) return false |
| | | try { |
| | | await ElMessageBox.confirm('确认å é¤è¯¥éä»¶ï¼', 'æç¤º', { type: 'warning' }) |
| | | } catch { |
| | | return false |
| | | } |
| | | await delRuleFile([row.id]) |
| | | ElMessage.success('å 餿å') |
| | | await refreshFileList() |
| | | } |
| | | |
| | | // è·åè§ç« å¶åº¦åè¡¨æ°æ® |
| | | const getRegulationList = async () => { |
| | | tableLoading.value = true |
| | | 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(() => { |
| | | // åå§å |
| | | getRegulationList() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .tab-content { |
| | | padding: 20px 0; |
| | | } |
| | | |
| | | .mb-20 { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .mt-20 { |
| | | margin-top: 20px; |
| | | } |
| | | |
| | | .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; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-card class="box-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>ç¨å°ç®¡çåå¸</span> |
| | | </div> |
| | | </template> |
| | | |
| | | |
| | | <!-- ç¨å°ç³è¯·ç®¡ç --> |
| | | <div class="tab-content"> |
| | | <el-row :gutter="20" class="mb-20 "> |
| | | <span class="ml-10">ç¨å°æ é¢ï¼</span> |
| | | <el-col :span="4"> |
| | | <el-input v-model="sealSearchForm.title" placeholder="请è¾å
¥ç³è¯·æ é¢" clearable /> |
| | | </el-col> |
| | | <span class="ml-10">ç¨å°ç¼å·ï¼</span> |
| | | <el-col :span="4"> |
| | | <el-input v-model="sealSearchForm.applicationNum" placeholder="请è¾å
¥ç¨å°ç¼å·" clearable /> |
| | | </el-col> |
| | | <span class="search_title">审æ¹ç¶æï¼</span> |
| | | <el-col :span="4"> |
| | | <el-select v-model="sealSearchForm.status" placeholder="审æ¹ç¶æ" clearable> |
| | | <el-option label="å¾
审æ¹" value="pending" /> |
| | | <el-option label="å·²éè¿" value="approved" /> |
| | | <el-option label="å·²æç»" value="rejected" /> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="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-form-item> |
| | | <el-form-item label="ç³è¯·æ é¢" prop="title"> |
| | | <el-input v-model="sealForm.title" placeholder="请è¾å
¥ç³è¯·æ é¢" /> |
| | | </el-form-item> |
| | | <el-form-item label="ç¨å°ç±»å" prop="sealType"> |
| | | <el-select v-model="sealForm.sealType" placeholder="è¯·éæ©ç¨å°ç±»å" style="width: 100%"> |
| | | <el-option label="å
¬ç« " value="official" /> |
| | | <el-option label="ååä¸ç¨ç« " value="contract" /> |
| | | <el-option label="è´¢å¡ä¸ç¨ç« " value="finance" /> |
| | | <el-option label="æ³äººç« " value="legal" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ç³è¯·åå " prop="reason"> |
| | | <el-input v-model="sealForm.reason" type="textarea" :rows="4" placeholder="请详ç»è¯´æç¨å°åå " /> |
| | | </el-form-item> |
| | | <el-form-item label="审æ¹äºº" prop="approveUserId"> |
| | | <el-select v-model="sealForm.approveUserId" placeholder="è¯·éæ©å®¡æ¹äºº" style="width: 100%" filterable> |
| | | <el-option |
| | | v-for="user in userList" |
| | | :key="user.userId" |
| | | :label="user.nickName" |
| | | :value="user.userId" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ç´§æ¥ç¨åº¦" prop="urgency"> |
| | | <el-radio-group v-model="sealForm.urgency"> |
| | | <el-radio label="normal">æ®é</el-radio> |
| | | <el-radio label="urgent">ç´§æ¥</el-radio> |
| | | <el-radio label="very-urgent">ç¹æ¥</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="showSealApplyDialog = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitSealApplication">æäº¤ç³è¯·</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- è§ç« å¶åº¦åå¸å¯¹è¯æ¡ --> |
| | | <!-- <el-dialog v-model="showRegulationDialog" :title="operationType === 'add' ? 'åå¸å¶åº¦' : 'ç¼è¾å¶åº¦'" width="800px"> |
| | | <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> |
| | | <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> |
| | | <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> |
| | | <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="hr">人äºé¨é¨</el-checkbox> |
| | | <el-checkbox label="finance">è´¢å¡é¨é¨</el-checkbox> |
| | | <el-checkbox label="tech">ææ¯é¨é¨</el-checkbox> |
| | | </el-checkbox-group> |
| | | </el-form-item> |
| | | <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 @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 :column="2" border> |
| | | <el-descriptions-item label="ç³è¯·ç¼å·">{{ currentSealDetail.id }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç³è¯·æ é¢">{{ currentSealDetail.title }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç³è¯·äºº">{{ currentSealDetail.createUserName }}</el-descriptions-item> |
| | | <el-descriptions-item label="æå±é¨é¨">{{ currentSealDetail.department }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç¨å°ç±»å">{{ getSealTypeText(currentSealDetail.sealType) }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç³è¯·æ¶é´">{{ currentSealDetail.createTime }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç¶æ"> |
| | | <el-tag :type="getStatusType(currentSealDetail.status)"> |
| | | {{ getStatusText(currentSealDetail.status) }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ç³è¯·åå " :span="2">{{ currentSealDetail.reason }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | </div> |
| | | </el-dialog> |
| | | |
| | | <!-- è§ç« å¶åº¦è¯¦æ
å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="showRegulationDetailDialog" title="è§ç« å¶åº¦è¯¦æ
" width="800px"> |
| | | <div v-if="currentRegulationDetail"> |
| | | <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> |
| | | <el-descriptions-item label="çæ¬">{{ currentRegulationDetail.version }}</el-descriptions-item> |
| | | <el-descriptions-item label="åå¸äºº">{{ currentRegulationDetail.createUserName }}</el-descriptions-item> |
| | | <el-descriptions-item label="å叿¶é´">{{ currentRegulationDetail.createTime }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | <div class="mt-20"> |
| | | <h4>å¶åº¦å
容</h4> |
| | | <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> |
| | | </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="åæ´è¯´æ"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'"> |
| | | {{ scope.row.status === 'active' ? 'çæä¸' : 'å·²åºæ¢' }} |
| | | </el-tag> |
| | | </template> |
| | | </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"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'confirmed' ? 'success' : 'warning'"> |
| | | {{ scope.row.status === 'confirmed' ? '已确认' : 'æªç¡®è®¤' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, getCurrentInstance, watch } from 'vue' |
| | | import { useRoute } from 'vue-router' |
| | | 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, userListNoPageByTenantId } 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 route = useRoute() |
| | | const showSealApplyDialog = ref(false) |
| | | const tableLoading = ref(false) |
| | | const showSealDetailDialog = ref(false) |
| | | const currentSealDetail = ref(null) |
| | | const sealFormRef = ref() |
| | | const userList = ref([]) |
| | | const sealForm = reactive({ |
| | | applicationNum: '', |
| | | title: '', |
| | | sealType: '', |
| | | reason: '', |
| | | approveUserId: '', |
| | | 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' }], |
| | | approveUserId: [{ required: true, message: 'è¯·éæ©å®¡æ¹äºº', trigger: 'change' }] |
| | | } |
| | | |
| | | const sealSearchForm = reactive({ |
| | | title: '', |
| | | status: '', |
| | | applicationNum: '' |
| | | }) |
| | | // å页忰 |
| | | 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 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 sealApplications = ref([]) |
| | | |
| | | const regulations = ref([]) |
| | | |
| | | const versionHistory = 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() |
| | | |
| | | // ElMessage.success('æç´¢å®æ') |
| | | } |
| | | // éç½®å°ç« ç³è¯·æç´¢ |
| | | const resetSealSearch = () => { |
| | | sealSearchForm.title = '' |
| | | sealSearchForm.status = '' |
| | | sealSearchForm.applicationNum = '' |
| | | 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: '', |
| | | approveUserId: '', |
| | | 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) |
| | | } |
| | | } |
| | | //éç½®å¶åº¦è¡¨å |
| | | const resetRegulationForm = () => { |
| | | Object.assign(regulationForm, { |
| | | id: '', |
| | | regulationNum: '', |
| | | title: '', |
| | | category: '', |
| | | content: '', |
| | | version: '', |
| | | status: 'active', |
| | | readCount: 0, |
| | | effectiveTime: '', |
| | | scope: [], |
| | | requireConfirm: 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 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 params = { |
| | | 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('å¶åº¦é
读æå') |
| | | } |
| | | }) |
| | | } |
| | | } |
| | | }) |
| | | |
| | | } |
| | | // æ¥çå¶åº¦é
è¯»ç¶æ |
| | | 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 { proxy } = getCurrentInstance() |
| | | const handleExport = () => { |
| | | proxy.download('/sealApplicationManagement/export', { ...sealSearchForm }, 'ç¨å°ç³è¯·.xlsx') |
| | | } |
| | | |
| | | // è·åå°ç« ç³è¯·åè¡¨æ°æ® |
| | | const getSealApplicationList = async () => { |
| | | tableLoading.value = true |
| | | listSealApplication(page,sealSearchForm) |
| | | .then(res => { |
| | | //è·åå½åç»å½çé¨é¨ä¿¡æ¯ |
| | | // è·åå½åç»å½çé¨é¨ä¿¡æ¯å¹¶è¿æ»¤æ°æ® |
| | | const currentFactoryName = userStore.currentFactoryName |
| | | if (currentFactoryName) { |
| | | // æ ¹æ®currentFactoryNameè¿æ»¤åºdepartmentç¸åçæ°æ® |
| | | sealApplications.value = res.data.records.filter(item => item.department === currentFactoryName) |
| | | // æ´æ°è¿æ»¤åçæ»æ° |
| | | page.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; |
| | | }) |
| | | } |
| | | |
| | | // çå¬å¯¹è¯æ¡æå¼ï¼è·åç¨æ·å表 |
| | | watch(showSealApplyDialog, (newVal) => { |
| | | if (newVal) { |
| | | userListNoPageByTenantId().then((res) => { |
| | | userList.value = res.data; |
| | | }); |
| | | } |
| | | }); |
| | | |
| | | onMounted(() => { |
| | | // è·¯ç±æºå¸¦ applicationNum æ¶ï¼é¢å¡«å¹¶æ¥è¯¢ |
| | | if (route.query.applicationNum) { |
| | | sealSearchForm.applicationNum = String(route.query.applicationNum) |
| | | page.current = 1 |
| | | getSealApplicationList() |
| | | } else { |
| | | getSealApplicationList() |
| | | } |
| | | getRegulationList() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .tab-content { |
| | | padding: 20px 0; |
| | | } |
| | | |
| | | .mb-20 { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .mt-20 { |
| | | margin-top: 20px; |
| | | } |
| | | |
| | | .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; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | </style> |
| | |
| | | <th>ç±»å</th> |
| | | <th>ç级</th> |
| | | <th>ç¶æ</th> |
| | | <th>责任人</th> |
| | | <th>æä½</th> |
| | | </tr> |
| | | </thead> |
| | |
| | | {{ warning.statusText }} |
| | | </span> |
| | | </td> |
| | | <td>{{ warning.responsible }}</td> |
| | | <td> |
| | | <button @click="viewDetail(warning)">æ¥ç详æ
</button> |
| | | </td> |
| | |
| | | levelText: '红è²é¢è¦', |
| | | status: 'pending', |
| | | statusText: 'å¾
å¤ç', |
| | | responsible: 'å¼ ç»ç', |
| | | responsible: 'éå¿å¼º', |
| | | description: 'A项ç®é¢ç®æ§è¡ç已达95%ï¼é¢è®¡å°è¶
åºé¢ç®èå´ã', |
| | | impact: 'å½±åé¡¹ç®æ´ä½è´¢å¡ææ ï¼å¯è½å¯¼è´é¡¹ç®äºæ', |
| | | suggestions: 'æåéå¿
è¦æ¯åºï¼ä¼åèµæºé
ç½®ï¼ç³è¯·é¢ç®è°æ´' |
| | |
| | | levelText: '红è²é¢è¦', |
| | | status: 'pending', |
| | | statusText: 'å¾
å¤ç', |
| | | responsible: 'éæ»ç', |
| | | responsible: 'éå¿å¼º', |
| | | description: '产åDå¨å®¢æ·ç°åºåºç°è´¨éé®é¢ã', |
| | | impact: 'å½±åå®¢æ·æ»¡æåº¦ï¼å¯è½é æç»æµæå¤±', |
| | | suggestions: 'ç«å³å¬åé®é¢äº§åï¼åæåå ï¼å¶å®æ¹è¿æªæ½' |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <el-form :model="searchForm" :inline="true"> |
| | | <el-form-item label="产å大类ï¼"> |
| | | <el-input |
| | | v-model="searchForm.productCategory" |
| | | placeholder="请è¾å
¥äº§å大类" |
| | | clearable |
| | | style="width: 200px" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="è§æ ¼åå·ï¼"> |
| | | <el-input |
| | | v-model="searchForm.specificationModel" |
| | | placeholder="请è¾å
¥è§æ ¼åå·" |
| | | clearable |
| | | style="width: 200px" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="é¢è¦çº§å«ï¼"> |
| | | <el-select |
| | | v-model="searchForm.warningLevel" |
| | | placeholder="è¯·éæ©é¢è¦çº§å«" |
| | | clearable |
| | | style="width: 150px" |
| | | > |
| | | <el-option label="ç´§æ¥" value="ç´§æ¥" /> |
| | | <el-option label="éè¦" value="éè¦" /> |
| | | <el-option label="ä¸è¬" value="ä¸è¬" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="é¢è¦ç¶æï¼"> |
| | | <el-select |
| | | v-model="searchForm.warningStatus" |
| | | placeholder="è¯·éæ©é¢è¦ç¶æ" |
| | | clearable |
| | | style="width: 150px" |
| | | > |
| | | <el-option label="å·²é¢è¦" value="å·²é¢è¦" /> |
| | | <el-option label="æ£å¸¸" value="æ£å¸¸" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleQuery">æç´¢</el-button> |
| | | <el-button @click="resetQuery">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | |
| | | <div class="table_list"> |
| | | <div class="actions"></div> |
| | | <el-table |
| | | :data="tableData" |
| | | border |
| | | v-loading="tableLoading" |
| | | style="width: 100%" |
| | | height="calc(100vh - 280px)" |
| | | > |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | <el-table-column label="æ¹æ¬¡å·" prop="code" width="130" show-overflow-tooltip /> |
| | | <el-table-column label="产å大类" prop="productCategory" show-overflow-tooltip /> |
| | | <el-table-column label="è§æ ¼åå·" prop="specificationModel" show-overflow-tooltip /> |
| | | <el-table-column label="å½ååºå" prop="currentStock" width="120" show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | <span :class="getStockClass(scope.row)">{{ scope.row.currentStock || 0 }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½åºå" prop="warnNum" width="120" show-overflow-tooltip /> |
| | | <el-table-column label="é¢è¦çº§å«" prop="warningLevel" width="100" show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | <el-tag :type="getWarningLevelTag(scope.row.warningLevel)"> |
| | | {{ scope.row.warningLevel || '-' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="é¢è¦ç¶æ" prop="warningStatus" width="100" show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.warningStatus === 'å·²é¢è¦' ? 'danger' : 'success'"> |
| | | {{ scope.row.warningStatus || 'æ£å¸¸' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="é¢è¦æ¶é´" prop="warningTime" width="150" show-overflow-tooltip /> |
| | | <el-table-column label="é¢è®¡ç¼ºè´§æ¶é´" prop="expectedShortageTime" width="150" show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | <div v-if="scope.row.expectedShortageTime"> |
| | | <div v-if="getCountdown(scope.row.expectedShortageTime).isExpired" class="countdown-expired"> |
| | | <el-tag type="danger">已缺货</el-tag> |
| | | </div> |
| | | <div v-else class="countdown-timer"> |
| | | <span :class="getCountdownClass(scope.row.expectedShortageTime)"> |
| | | {{ getCountdown(scope.row.expectedShortageTime).text }} |
| | | </span> |
| | | </div> |
| | | </div> |
| | | <span v-else>-</span> |
| | | </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> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import pagination from '@/components/PIMTable/Pagination.vue' |
| | | import { |
| | | getStockWarningLedgerPage |
| | | } from '@/api/inventoryManagement/stockWarningLedger.js' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const tableData = ref([]) |
| | | const tableLoading = ref(false) |
| | | const total = ref(0) |
| | | |
| | | // å页忰 |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100 |
| | | }) |
| | | |
| | | // æç´¢è¡¨å |
| | | const searchForm = reactive({ |
| | | productCategory: '', |
| | | specificationModel: '', |
| | | warningLevel: '', |
| | | warningStatus: '' |
| | | }) |
| | | |
| | | // è·ååè¡¨æ°æ® |
| | | const getList = () => { |
| | | tableLoading.value = true |
| | | const params = { |
| | | ...page, |
| | | ...searchForm |
| | | } |
| | | getStockWarningLedgerPage(params) |
| | | .then(res => { |
| | | tableLoading.value = false |
| | | if (res.code === 200) { |
| | | tableData.value = res.data.records || [] |
| | | total.value = res.data.total || 0 |
| | | |
| | | // 计ç®é¢è¦çº§å«åç¶æ |
| | | tableData.value = tableData.value.map(item => { |
| | | const currentStock = parseFloat(item.inboundNum0 || item.currentStock || 0) |
| | | const warnNum = parseFloat(item.warnNum || 0) |
| | | const safetyStock = parseFloat(item.safetyStock || warnNum * 1.2) |
| | | |
| | | // 计ç®é¢è¦çº§å« |
| | | if (currentStock <= 0) { |
| | | item.warningLevel = 'ç´§æ¥' |
| | | item.warningStatus = 'å·²é¢è¦' |
| | | } else if (currentStock < warnNum) { |
| | | item.warningLevel = 'éè¦' |
| | | item.warningStatus = 'å·²é¢è¦' |
| | | } else if (currentStock < safetyStock) { |
| | | item.warningLevel = 'ä¸è¬' |
| | | item.warningStatus = 'å·²é¢è¦' |
| | | } else { |
| | | item.warningLevel = '' |
| | | item.warningStatus = 'æ£å¸¸' |
| | | } |
| | | |
| | | // 计ç®é¢è®¡ç¼ºè´§æ¶é´ï¼åºäºæ¥åæ¶èéï¼è¿éç®åå¤çï¼ |
| | | if (item.warningStatus === 'å·²é¢è¦' && currentStock > 0 && warnNum > 0) { |
| | | const dailyConsumption = warnNum / 30 // å设30天æ¶è宿ä½åºå |
| | | const daysRemaining = Math.floor(currentStock / dailyConsumption) |
| | | if (daysRemaining > 0) { |
| | | const date = new Date() |
| | | date.setDate(date.getDate() + daysRemaining) |
| | | item.expectedShortageTime = date.toISOString().split('T')[0] |
| | | } |
| | | } |
| | | |
| | | item.currentStock = currentStock |
| | | item.safetyStock = safetyStock |
| | | |
| | | return item |
| | | }) |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | tableLoading.value = false |
| | | ElMessage.error(err.msg || 'è·åæ°æ®å¤±è´¥') |
| | | }) |
| | | } |
| | | |
| | | // æç´¢ |
| | | const handleQuery = () => { |
| | | page.current = 1 |
| | | getList() |
| | | } |
| | | |
| | | // éç½®æç´¢ |
| | | const resetQuery = () => { |
| | | Object.keys(searchForm).forEach(key => { |
| | | searchForm[key] = '' |
| | | }) |
| | | handleQuery() |
| | | } |
| | | |
| | | // å页åå |
| | | const paginationChange = (obj) => { |
| | | page.current = obj.page |
| | | page.size = obj.limit |
| | | getList() |
| | | } |
| | | |
| | | // è·ååºåæ ·å¼ç±» |
| | | const getStockClass = (row) => { |
| | | const currentStock = parseFloat(row.currentStock || row.inboundNum0 || 0) |
| | | const warnNum = parseFloat(row.warnNum || 0) |
| | | |
| | | if (currentStock <= 0) { |
| | | return 'text-danger' |
| | | } else if (currentStock < warnNum) { |
| | | return 'text-warning' |
| | | } |
| | | return 'text-success' |
| | | } |
| | | |
| | | // è·åé¢è¦çº§å«æ ç¾æ ·å¼ |
| | | const getWarningLevelTag = (level) => { |
| | | const levelMap = { |
| | | 'ç´§æ¥': 'danger', |
| | | 'éè¦': 'warning', |
| | | 'ä¸è¬': 'info' |
| | | } |
| | | return levelMap[level] || 'info' |
| | | } |
| | | |
| | | // è·åå计æ¶ä¿¡æ¯ |
| | | const getCountdown = (expectedTime) => { |
| | | if (!expectedTime) return { text: '-', isExpired: false } |
| | | |
| | | const now = new Date().getTime() |
| | | const expected = new Date(expectedTime).getTime() |
| | | const diff = expected - now |
| | | |
| | | if (diff <= 0) { |
| | | return { text: '已缺货', isExpired: true } |
| | | } |
| | | |
| | | const days = Math.floor(diff / (1000 * 60 * 60 * 24)) |
| | | const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)) |
| | | const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)) |
| | | |
| | | if (days > 0) { |
| | | return { text: `${days}天${hours}å°æ¶`, isExpired: false } |
| | | } else if (hours > 0) { |
| | | return { text: `${hours}å°æ¶${minutes}åé`, isExpired: false } |
| | | } else { |
| | | return { text: `${minutes}åé`, isExpired: false } |
| | | } |
| | | } |
| | | |
| | | // è·ååè®¡æ¶æ ·å¼ç±» |
| | | const getCountdownClass = (expectedTime) => { |
| | | if (!expectedTime) return '' |
| | | |
| | | const now = new Date().getTime() |
| | | const expected = new Date(expectedTime).getTime() |
| | | const diff = expected - now |
| | | |
| | | if (diff <= 0) { |
| | | return 'countdown-expired' |
| | | } else if (diff <= 24 * 60 * 60 * 1000) { // 24å°æ¶å
|
| | | return 'countdown-urgent' |
| | | } else if (diff <= 7 * 24 * 60 * 60 * 1000) { // 7天å
|
| | | return 'countdown-warning' |
| | | } else { |
| | | return 'countdown-normal' |
| | | } |
| | | } |
| | | |
| | | // 页é¢å è½½ |
| | | onMounted(() => { |
| | | getList() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .app-container { |
| | | padding: 20px; |
| | | |
| | | .search_form { |
| | | background: #fff; |
| | | padding: 20px; |
| | | border-radius: 4px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .table_list { |
| | | background: #fff; |
| | | border-radius: 4px; |
| | | padding: 20px; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 20px; |
| | | } |
| | | } |
| | | |
| | | .text-danger { |
| | | color: #f56c6c; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .text-warning { |
| | | color: #e6a23c; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .text-success { |
| | | color: #67c23a; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .countdown-timer { |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .countdown-normal { |
| | | color: #67c23a; |
| | | } |
| | | |
| | | .countdown-warning { |
| | | color: #e6a23c; |
| | | } |
| | | |
| | | .countdown-urgent { |
| | | color: #f56c6c; |
| | | animation: blink 1s infinite; |
| | | } |
| | | |
| | | .countdown-expired { |
| | | color: #f56c6c; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | @keyframes blink { |
| | | 0%, 50% { opacity: 1; } |
| | | 51%, 100% { opacity: 0.5; } |
| | | } |
| | | } |
| | | </style> |
| | |
| | | <div> |
| | | <el-dialog |
| | | v-model="dialogFormVisible" |
| | | :title="operationType === 'add' ? 'æ°å¢å
¥è' : 'ç¼è¾äººå'" |
| | | :title="operationType === 'add' ? 'æ°å¢èªèµ' : 'ç¼è¾èªèµ'" |
| | | width="50%" |
| | | @close="closeDia" |
| | | > |
| | |
| | | > |
| | | </div> |
| | | <div> |
| | | <el-button @click="handleExport" style="margin-right: 10px">导åº</el-button> |
| | | <el-button type="primary" @click="openForm('add')">æ°å¢èªèµ</el-button> |
| | | <!-- <el-button @click="handleOut">导åº</el-button>--> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | | </div> |
| | |
| | | |
| | | <script setup> |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import {onMounted, ref} from "vue"; |
| | | import {onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue"; |
| | | import FormDia from "@/views/personnelManagement/payrollManagement/components/formDia.vue"; |
| | | import {staffJoinDel} from "@/api/personnelManagement/onboarding.js"; |
| | | import {ElMessageBox} from "element-plus"; |
| | |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | |
| | | // 导åºèªèµç®¡ç |
| | | const handleExport = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/compensationPerformance/export", { ...searchForm.value, ...page }, "èªèµç®¡ç.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |