| | |
| | | "dependencies": { |
| | | "@chenfengyuan/vue-qrcode": "^2.0.0", |
| | | "@element-plus/icons-vue": "2.3.1", |
| | | "@vue-office/docx": "^1.6.3", |
| | | "@vue-office/excel": "^1.7.14", |
| | | "@vueup/vue-quill": "1.2.0", |
| | | "@vueuse/core": "10.11.0", |
| | | "axios": "0.28.1", |
¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | // æ¥è¯¢å表 |
| | | export const listPage = (params) => { |
| | | return request({ |
| | | url: "/account/accountExpense/listPage", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | // æ°å¢ |
| | | export function add(data) { |
| | | return request({ |
| | | url: "/account/accountExpense/add", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // ç¼è¾ |
| | | export function update(data) { |
| | | return request({ |
| | | url: "/account/accountExpense/update", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | //å¯¼åº |
| | | export const exportAccountExpense = (query) => { |
| | | return request({ |
| | | url: "/account/accountExpense/export", |
| | | method: "post", |
| | | data: query, |
| | | responseType: "blob", |
| | | }); |
| | | }; |
| | | |
| | | export const delAccountExpense = (query) => { |
| | | return request({ |
| | | url: `account/accountExpense/del`, |
| | | method: "delete", |
| | | data: query, |
| | | }); |
| | | }; |
| | | |
| | | export const getAccountExpense = (id) => { |
| | | return request({ |
| | | url: `/account/accountExpense/${id}`, |
| | | method: "get", |
| | | }); |
| | | }; |
| | | |
| | | // æ¥è¯¢éä»¶å表 |
| | | export function fileListPage(query) { |
| | | return request({ |
| | | url: "/account/accountFile/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | // ä¿åéä»¶å表 |
| | | export function fileAdd(query) { |
| | | return request({ |
| | | url: "/account/accountFile/add", |
| | | method: "post", |
| | | data: query, |
| | | }); |
| | | } |
| | | // å é¤éä»¶å表 |
| | | export function fileDel(query) { |
| | | return request({ |
| | | url: "/account/accountFile/del", |
| | | method: "delete", |
| | | data: query, |
| | | }); |
| | | } |
¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | // æ ¹æ®æ¥ææ¥è¯¢ |
| | | export const reportForms = (params) => { |
| | | console.log(params); |
| | | return request({ |
| | | url: "/account/accountExpense/report/forms", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | // æ¥è¯¢æ¯ææ°æ®-æ¶å
¥ |
| | | export const reportIncome = (params) => { |
| | | console.log(params); |
| | | return request({ |
| | | url: "/account/accountExpense/report/income", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | // æ¥è¯¢æ¯ææ°æ®-æ¯åº |
| | | export const reportExpense = (params) => { |
| | | console.log(params); |
| | | return request({ |
| | | url: "/account/accountExpense/report/expense", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | // æ¥è¯¢å表 |
| | | export const listPage = (params) => { |
| | | return request({ |
| | | url: "/account/accountIncome/listPage", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | // æ°å¢ |
| | | export function add(data) { |
| | | return request({ |
| | | url: "/account/accountIncome/add", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // ç¼è¾ |
| | | export function update(data) { |
| | | return request({ |
| | | url: "/account/accountIncome/update", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | //å¯¼åº |
| | | export const exportAccountIncome = (query) => { |
| | | return request({ |
| | | url: "/account/accountIncome/export", |
| | | method: "post", |
| | | data: query, |
| | | responseType: "blob", |
| | | }); |
| | | }; |
| | | |
| | | export const delAccountIncome = (query) => { |
| | | return request({ |
| | | url: `account/accountIncome/del`, |
| | | method: "delete", |
| | | data: query, |
| | | }); |
| | | }; |
| | | |
| | | export const getAccountIncome = (id) => { |
| | | return request({ |
| | | url: `/account/accountIncome/${id}`, |
| | | method: "get", |
| | | }); |
| | | }; |
| | | |
| | | // æ¥è¯¢éä»¶å表 |
| | | export function fileListPage(query) { |
| | | return request({ |
| | | url: "/account/accountFile/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | // ä¿åéä»¶å表 |
| | | export function fileAdd(query) { |
| | | return request({ |
| | | url: "/account/accountFile/add", |
| | | method: "post", |
| | | data: query, |
| | | }); |
| | | } |
| | | // å é¤éä»¶å表 |
| | | export function fileDel(query) { |
| | | return request({ |
| | | url: "/account/accountFile/del", |
| | | method: "delete", |
| | | data: query, |
| | | }); |
| | | } |
¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from '@/utils/request' |
| | | |
| | | // æ¥è¯¢å¨èåå·¥å°è´¦ |
| | | export function staffOnJobListPage(query) { |
| | | return request({ |
| | | url: '/staff/staffOnJob/listPage', |
| | | method: 'get', |
| | | params: query, |
| | | }) |
| | | } |
| | | // æ¥è¯¢åå·¥å
¥èä¿¡æ¯ |
| | | export function staffOnJobInfo(query) { |
| | | return request({ |
| | | url: '/staff/staffOnJob/staffNo', |
| | | method: 'get', |
| | | params: query, |
| | | }) |
| | | } |
¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | // æ¥è¯¢äººåå
¥èå表 |
| | | export function staffJoinListPage(query) { |
| | | return request({ |
| | | url: "/staff/staffJoinLeaveRecord/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | // æ°å¢äººåå
¥è |
| | | export function staffJoinAdd(query) { |
| | | return request({ |
| | | url: "/staff/staffJoinLeaveRecord/add", |
| | | method: "post", |
| | | data: query, |
| | | }); |
| | | } |
| | | // ä¿®æ¹äººåå
¥è |
| | | export function staffJoinUpdate(query) { |
| | | return request({ |
| | | url: "/staff/staffJoinLeaveRecord/update", |
| | | method: "post", |
| | | data: query, |
| | | }); |
| | | } |
| | | // æ¥è¯¢åå·¥å
¥èä¿¡æ¯ |
| | | export function getStaffJoinInfo(query) { |
| | | return request({ |
| | | url: "/staff/staffJoinLeaveRecord/" + query, |
| | | method: "get", |
| | | data: query, |
| | | }); |
| | | } |
| | | // å é¤åå·¥ |
| | | export function staffJoinDel(query) { |
| | | return request({ |
| | | url: "/staff/staffJoinLeaveRecord/del", |
| | | method: "delete", |
| | | data: query, |
| | | }); |
| | | } |
| | | |
| | | export function getStaffOnJob() { |
| | | return request({ |
| | | url: "/staff/staffOnJob/list", |
| | | method: "get", |
| | | }); |
| | | } |
¶Ô±ÈÐÂÎļþ |
| | |
| | | // èªé
¬ç®¡ç |
| | | import request from "@/utils/request"; |
| | | |
| | | // æ¥è¯¢å表 |
| | | export function compensationListPage(query) { |
| | | return request({ |
| | | url: "/compensationPerformance/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | // æ°å¢ |
| | | export function compensationAdd(query) { |
| | | return request({ |
| | | url: "/compensationPerformance/add", |
| | | method: "post", |
| | | data: query, |
| | | }); |
| | | } |
| | | // ä¿®æ¹ |
| | | export function compensationUpdate(query) { |
| | | return request({ |
| | | url: "/compensationPerformance/update", |
| | | method: "post", |
| | | data: query, |
| | | }); |
| | | } |
| | | // å é¤ |
| | | export function compensationDelete(query) { |
| | | return request({ |
| | | url: "/compensationPerformance/delete", |
| | | method: "delete", |
| | | data: query, |
| | | }); |
| | | } |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <el-table |
| | | ref="multipleTable" |
| | | v-loading="tableLoading" |
| | | :border="border" |
| | | :data="tableData" |
| | | :header-cell-style="{ background: '#F0F1F5', color: '#333333' }" |
| | | :height="height" |
| | | :highlight-current-row="highlightCurrentRow" |
| | | :row-class-name="rowClassName" |
| | | :row-style="rowStyle" |
| | | :row-key="rowKey" |
| | | style="width: 100%" |
| | | tooltip-effect="dark" |
| | | :expand-row-keys="expandRowKeys" |
| | | :show-summary="isShowSummary" |
| | | :summary-method="summaryMethod" |
| | | @row-click="rowClick" |
| | | @current-change="currentChange" |
| | | @selection-change="handleSelectionChange" |
| | | @expand-change="expandChange" |
| | | class="lims-table" |
| | | > |
| | | <el-table-column |
| | | align="center" |
| | | type="selection" |
| | | width="55" |
| | | v-if="isSelection" |
| | | /> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | |
| | | <el-table-column |
| | | v-for="(item, index) in column" |
| | | :key="index" |
| | | :column-key="item.columnKey" |
| | | :filter-method="item.filterHandler" |
| | | :filter-multiple="item.filterMultiple" |
| | | :filtered-value="item.filteredValue" |
| | | :filters="item.filters" |
| | | :fixed="item.fixed" |
| | | :label="item.label" |
| | | :prop="item.prop" |
| | | show-overflow-tooltip |
| | | :align="item.align" |
| | | :sortable="!!item.sortable" |
| | | :type="item.type" |
| | | :width="item.width" |
| | | > |
| | | <template |
| | | v-if="item.hasOwnProperty('colunmTemplate')" |
| | | #[item.colunmTemplate]="scope" |
| | | > |
| | | <slot |
| | | v-if="item.theadSlot" |
| | | :name="item.theadSlot" |
| | | :index="scope.$index" |
| | | :row="scope.row" |
| | | /> |
| | | </template> |
| | | |
| | | <template #default="scope"> |
| | | <!-- ææ§½ --> |
| | | <div v-if="item.dataType == 'slot'"> |
| | | <slot |
| | | v-if="item.slot" |
| | | :index="scope.$index" |
| | | :name="item.slot" |
| | | :row="scope.row" |
| | | /> |
| | | </div> |
| | | <!-- è¿åº¦æ¡ --> |
| | | <div v-else-if="item.dataType == 'progress'"> |
| | | <el-progress :percentage="Number(scope.row[item.prop])" /> |
| | | </div> |
| | | <!-- å¾ç --> |
| | | <div v-else-if="item.dataType == 'image'"> |
| | | <img |
| | | :src="javaApi + '/img/' + scope.row[item.prop]" |
| | | alt="" |
| | | style="width: 40px; height: 40px; margin-top: 10px" |
| | | /> |
| | | </div> |
| | | |
| | | <!-- tag --> |
| | | <div v-else-if="item.dataType == 'tag'"> |
| | | <el-tag |
| | | v-if=" |
| | | typeof dataTypeFn(scope.row[item.prop], item.formatData) === |
| | | 'string' |
| | | " |
| | | :title="formatters(scope.row[item.prop], item.formatData)" |
| | | :type="formatType(scope.row[item.prop], item.formatType)" |
| | | > |
| | | {{ formatters(scope.row[item.prop], item.formatData) }} |
| | | </el-tag> |
| | | |
| | | <el-tag |
| | | v-for="(tag, index) in dataTypeFn( |
| | | scope.row[item.prop], |
| | | item.formatData |
| | | )" |
| | | v-else-if=" |
| | | typeof dataTypeFn(scope.row[item.prop], item.formatData) === |
| | | 'object' |
| | | " |
| | | :key="index" |
| | | :title="formatters(scope.row[item.prop], item.formatData)" |
| | | :type="formatType(tag, item.formatType)" |
| | | > |
| | | {{ item.tagGroup ? tag[item.tagGroup.label] ?? tag : tag }} |
| | | </el-tag> |
| | | |
| | | <el-tag |
| | | v-else |
| | | :title="formatters(scope.row[item.prop], item.formatData)" |
| | | :type="formatType(scope.row[item.prop], item.formatType)" |
| | | > |
| | | {{ formatters(scope.row[item.prop], item.formatData) }} |
| | | </el-tag> |
| | | </div> |
| | | |
| | | <!-- æé® --> |
| | | <div v-else-if="item.dataType == 'action'"> |
| | | <template v-for="(o, key) in item.operation" :key="key"> |
| | | <el-button |
| | | v-show="o.type != 'upload'" |
| | | size="small" |
| | | v-if="o.showHide ? o.showHide(scope.row) : true" |
| | | :disabled="o.disabled ? o.disabled(scope.row) : false" |
| | | :plain="o.plain" |
| | | type="primary" |
| | | :style="{ |
| | | color: |
| | | o.name === 'å é¤' || o.name === 'delete' |
| | | ? '#f56c6c' |
| | | : o.color, |
| | | }" |
| | | link |
| | | @click="o.clickFun(scope.row)" |
| | | :key="key" |
| | | > |
| | | {{ o.name }} |
| | | </el-button> |
| | | <el-upload |
| | | :action=" |
| | | javaApi + |
| | | o.url + |
| | | '?id=' + |
| | | (o.uploadIdFun ? o.uploadIdFun(scope.row) : scope.row.id) |
| | | " |
| | | ref="uploadRef" |
| | | size="small" |
| | | :multiple="o.multiple ? o.multiple : false" |
| | | :limit="1" |
| | | :disabled="o.disabled ? o.disabled(scope.row) : false" |
| | | :accept=" |
| | | o.accept |
| | | ? o.accept |
| | | : '.jpg,.jpeg,.png,.gif,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.zip,.rar' |
| | | " |
| | | v-if="o.type == 'upload'" |
| | | style="display: inline-block; width: 50px" |
| | | v-show="o.showHide ? o.showHide(scope.row) : true" |
| | | :headers="uploadHeader" |
| | | :before-upload="(file) => beforeUpload(file, scope.$index)" |
| | | :on-change=" |
| | | (file, fileList) => handleChange(file, fileList, scope.$index) |
| | | " |
| | | :on-error=" |
| | | (error, file, fileList) => |
| | | onError(error, file, fileList, scope.$index) |
| | | " |
| | | :on-success=" |
| | | (response, file, fileList) => |
| | | handleSuccessUp(response, file, fileList, scope.$index) |
| | | " |
| | | :on-exceed="onExceed" |
| | | :show-file-list="false" |
| | | > |
| | | <el-button |
| | | :size="o.size ? o.size : 'small'" |
| | | link |
| | | type="primary" |
| | | :disabled="o.disabled ? o.disabled(scope.row) : false" |
| | | >{{ o.name }}</el-button |
| | | > |
| | | </el-upload> |
| | | </template> |
| | | </div> |
| | | <!-- å¯ç¹å»çæå --> |
| | | <div |
| | | v-else-if="item.dataType == 'link'" |
| | | class="cell link" |
| | | style="width: 100%" |
| | | @click="goLink(scope.row, item.linkMethod)" |
| | | > |
| | | <span v-if="!item.formatData">{{ scope.row[item.prop] }}</span> |
| | | </div> |
| | | <!-- é»è®¤çº¯å±ç¤ºæ°æ® --> |
| | | <div v-else class="cell" style="width: 100%"> |
| | | <span v-if="!item.formatData">{{ scope.row[item.prop] }}</span> |
| | | <span v-else>{{ |
| | | formatters(scope.row[item.prop], item.formatData) |
| | | }}</span> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <pagination |
| | | v-if="page.total > 0" |
| | | :total="page.total" |
| | | :layout="page.layout" |
| | | :page="page.current" |
| | | :limit="page.size" |
| | | @pagination="paginationSearch" |
| | | /> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import pagination from "./Pagination.vue"; |
| | | import { ref, inject, getCurrentInstance } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | |
| | | // è·åå
¨å±ç uploadHeader |
| | | const { proxy } = getCurrentInstance(); |
| | | const uploadHeader = proxy.uploadHeader; |
| | | const javaApi = proxy.javaApi; |
| | | |
| | | const emit = defineEmits(["pagination", "expand-change", "selection-change"]); |
| | | |
| | | // Filters |
| | | const typeFn = (val, row) => { |
| | | return typeof val === "function" ? val(row) : val; |
| | | }; |
| | | |
| | | const formatters = (val, format) => { |
| | | return typeof format === "function" ? format(val) : val; |
| | | }; |
| | | |
| | | // Propsï¼ä½¿ç¨ defineProps çé TS å½¢å¼ï¼ |
| | | const props = defineProps({ |
| | | tableLoading: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | height: { |
| | | type: [Number, String], |
| | | default: "calc(100vh - 22em)", |
| | | }, |
| | | expandRowKeys: { |
| | | type: Array, |
| | | default: () => [], |
| | | }, |
| | | summaryMethod: { |
| | | type: Function, |
| | | default: () => {}, |
| | | }, |
| | | rowClick: { |
| | | type: Function, |
| | | default: () => {}, |
| | | }, |
| | | currentChange: { |
| | | type: Function, |
| | | default: () => {}, |
| | | }, |
| | | border: { |
| | | type: Boolean, |
| | | default: true, |
| | | }, |
| | | isSelection: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | isShowSummary: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | highlightCurrentRow: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | headerCellStyle: { |
| | | type: Object, |
| | | default: () => ({}), |
| | | }, |
| | | column: { |
| | | type: Array, |
| | | default: () => [], |
| | | }, |
| | | rowClassName: { |
| | | type: Function, |
| | | default: () => "", |
| | | }, |
| | | rowStyle: { |
| | | type: [Object, Function], |
| | | default: () => ({}), |
| | | }, |
| | | tableData: { |
| | | type: Array, |
| | | default: () => [], |
| | | }, |
| | | rowKey: { |
| | | type: String, |
| | | default: 'id', |
| | | }, |
| | | page: { |
| | | type: Object, |
| | | default: () => ({ |
| | | total: 0, |
| | | current: 0, |
| | | size: 10, |
| | | layout: "total, sizes, prev, pager, next, jumper", |
| | | }), |
| | | }, |
| | | total: { |
| | | type: Number, |
| | | default: 0, |
| | | }, |
| | | }); |
| | | |
| | | // Data |
| | | const uploadRefs = ref([]); |
| | | const currentFiles = ref({}); |
| | | const uploadKeys = ref({}); |
| | | |
| | | const indexMethod = (index) => { |
| | | return (props.page.current - 1) * props.page.size + index + 1; |
| | | }; |
| | | |
| | | // ç¹å» link äºä»¶ |
| | | const goLink = (row, linkMethod) => { |
| | | if (!linkMethod) { |
| | | return ElMessage.warning("请é
ç½® link äºä»¶"); |
| | | } |
| | | const parentMethod = getParentMethod(linkMethod); |
| | | if (typeof parentMethod === "function") { |
| | | parentMethod(row); |
| | | } else { |
| | | console.warn(`ç¶ç»ä»¶ä¸æªæ¾å°æ¹æ³: ${linkMethod}`); |
| | | } |
| | | }; |
| | | |
| | | // è·åç¶ç»ä»¶æ¹æ³ï¼ç¤ºä¾å®ç°ï¼ |
| | | const getParentMethod = (methodName) => { |
| | | const parentMethods = inject("parentMethods", {}); |
| | | return parentMethods[methodName]; |
| | | }; |
| | | |
| | | const dataTypeFn = (val, format) => { |
| | | if (typeof format === "function") { |
| | | return format(val); |
| | | } else return val; |
| | | }; |
| | | |
| | | const formatType = (val, format) => { |
| | | if (typeof format === "function") { |
| | | return format(val); |
| | | } else return ""; |
| | | }; |
| | | |
| | | // æä»¶ååå¤ç |
| | | const handleChange = (file, fileList, index) => { |
| | | if (fileList.length > 1) { |
| | | const earliestFile = fileList[0]; |
| | | uploadRefs.value[index]?.handleRemove(earliestFile); |
| | | } |
| | | currentFiles.value[index] = file; |
| | | }; |
| | | |
| | | // æä»¶ä¸ä¼ åæ ¡éª |
| | | const beforeUpload = (rawFile, index) => { |
| | | currentFiles.value[index] = {}; |
| | | if (rawfile.size > 1024 * 1024 * 10 * 10) { |
| | | ElMessage.error("ä¸ä¼ æä»¶ä¸è¶
è¿10M"); |
| | | return false; |
| | | } |
| | | return true; |
| | | }; |
| | | |
| | | // ä¸ä¼ æå |
| | | const handleSuccessUp = (response, file, fileList, index) => { |
| | | if (response.code == 200) { |
| | | if (uploadRefs[index]) { |
| | | uploadRefs[index].clearFiles(); |
| | | } |
| | | currentFiles[index] = file; |
| | | ElMessage.success("ä¸ä¼ æå"); |
| | | resetUploadComponent(index); |
| | | } else { |
| | | ElMessage.error(response.message); |
| | | } |
| | | }; |
| | | |
| | | const resetUploadComponent = (index) => { |
| | | uploadKeys[index] = Date.now(); |
| | | }; |
| | | |
| | | // ä¸ä¼ 失败 |
| | | const onError = (error, file, fileList, index) => { |
| | | ElMessage.error("æä»¶ä¸ä¼ 失败ï¼è¯·éè¯"); |
| | | if (uploadRefs.value[index]) { |
| | | uploadRefs.value[index].clearFiles(); |
| | | } |
| | | }; |
| | | |
| | | // æä»¶æ°éè¶
éæç¤º |
| | | const onExceed = () => { |
| | | ElMessage.warning("è¶
åºæä»¶ä¸ªæ°"); |
| | | }; |
| | | |
| | | const paginationSearch = ({ page, limit }) => { |
| | | emit("pagination", { page: page, limit: limit }); |
| | | }; |
| | | |
| | | const expandChange = (row, expandedRows) => { |
| | | emit("expand-change", row, expandedRows); |
| | | }; |
| | | |
| | | const handleSelectionChange = (newSelection) => { |
| | | emit("selection-change", newSelection); |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .cell { |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | padding-right: 0 !important; |
| | | padding-left: 0 !important; |
| | | } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div :class="{ hidden }" class="pagination-container"> |
| | | <el-pagination |
| | | :background="background" |
| | | v-model:current-page="currentPage" |
| | | v-model:page-size="pageSize" |
| | | :layout="layout" |
| | | :page-sizes="pageSizes" |
| | | :pager-count="pagerCount" |
| | | :total="total" |
| | | v-bind="$attrs" |
| | | @size-change="handleSizeChange" |
| | | @current-change="handleCurrentChange" |
| | | /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed } from 'vue' |
| | | import { scrollTo } from '@/utils/scroll-to' |
| | | |
| | | const props = defineProps({ |
| | | total: { |
| | | type: Number, |
| | | required: true |
| | | }, |
| | | page: { |
| | | type: Number, |
| | | default: 1 |
| | | }, |
| | | limit: { |
| | | type: Number, |
| | | default: 20 |
| | | }, |
| | | pageSizes: { |
| | | type: Array, |
| | | default: () => [10, 20, 30, 50, 100] |
| | | }, |
| | | pagerCount: { |
| | | type: Number, |
| | | default: () => (document.body.clientWidth < 992 ? 5 : 7) |
| | | }, |
| | | layout: { |
| | | type: String, |
| | | default: 'total, sizes, prev, pager, next, jumper' |
| | | }, |
| | | background: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | autoScroll: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | hidden: { |
| | | type: Boolean, |
| | | default: false |
| | | } |
| | | }) |
| | | |
| | | const emit = defineEmits(['update:page', 'update:limit', 'pagination']) |
| | | |
| | | const currentPage = computed({ |
| | | get: () => props.page, |
| | | set: (val) => emit('update:page', val) |
| | | }) |
| | | |
| | | const pageSize = computed({ |
| | | get: () => props.limit, |
| | | set: (val) => emit('update:limit', val) |
| | | }) |
| | | |
| | | const handleSizeChange = (val) => { |
| | | if (currentPage.value * val > props.total) { |
| | | currentPage.value = 1 |
| | | } |
| | | emit('pagination', { page: currentPage.value, limit: val }) |
| | | if (props.autoScroll) { |
| | | scrollTo(0, 800) |
| | | } |
| | | } |
| | | |
| | | const handleCurrentChange = (val) => { |
| | | emit('pagination', { page: val, limit: pageSize.value }) |
| | | if (props.autoScroll) { |
| | | scrollTo(0, 800) |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .pagination-container { |
| | | background: #fff; |
| | | padding: 16px 0; |
| | | margin-top: 0; |
| | | } |
| | | .pagination-container.hidden { |
| | | display: none; |
| | | } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <el-dialog v-model="dialogVisible" title="é¢è§" width="100%" fullscreen align-center :before-close="handleClose" append-to-body> |
| | | <div> |
| | | <!-- å¾çé¢è§ --> |
| | | <div v-if="isImage"> |
| | | <img :src="imgUrl" alt="Image Preview" /> |
| | | </div> |
| | | |
| | | <!-- PDFé¢è§æç¤º --> |
| | | <div v-if="isPdf" style="height: 100vh; display: flex; align-items: center; justify-content: center;"> |
| | | <p>æ£å¨åå¤PDFé¢è§...</p> |
| | | </div> |
| | | |
| | | <!-- Wordææ¡£é¢è§ --> |
| | | <div v-if="isDoc"> |
| | | <p v-if="!isDocShow">ææ¡£æ æ³ç´æ¥é¢è§ï¼è¯·ä¸è½½æ¥çã</p> |
| | | <a :href="fileUrl" v-if="!isDocShow">ä¸è½½æä»¶</a> |
| | | <vue-office-docx |
| | | v-else |
| | | :src="fileUrl" |
| | | style="height: 100vh;" |
| | | @rendered="renderedHandler" |
| | | @error="errorHandler" |
| | | /> |
| | | </div> |
| | | |
| | | <!-- Excelææ¡£é¢è§ --> |
| | | <div v-if="isXls"> |
| | | <p v-if="!isDocShow">ææ¡£æ æ³ç´æ¥é¢è§ï¼è¯·ä¸è½½æ¥çã</p> |
| | | <a :href="fileUrl" v-if="!isDocShow">ä¸è½½æä»¶</a> |
| | | <vue-office-excel |
| | | v-else |
| | | :src="fileUrl" |
| | | :options="options" |
| | | style="height: 100vh;" |
| | | @rendered="renderedHandler" |
| | | @error="errorHandler" |
| | | /> |
| | | </div> |
| | | |
| | | <!-- å缩æä»¶å¤ç --> |
| | | <div v-if="isZipOrRar"> |
| | | <p>å缩æä»¶æ æ³ç´æ¥é¢è§ï¼è¯·ä¸è½½æ¥çã</p> |
| | | <a :href="fileUrl">ä¸è½½æä»¶</a> |
| | | </div> |
| | | |
| | | <!-- 䏿¯æçæ ¼å¼ --> |
| | | <div v-if="!isSupported"> |
| | | <p>䏿¯æçæä»¶æ ¼å¼</p> |
| | | </div> |
| | | </div> |
| | | </el-dialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, getCurrentInstance, watch } from 'vue'; |
| | | import VueOfficeDocx from '@vue-office/docx'; |
| | | import '@vue-office/docx/lib/index.css'; |
| | | import VueOfficeExcel from '@vue-office/excel'; |
| | | import '@vue-office/excel/lib/index.css'; |
| | | |
| | | // ååºå¼åé |
| | | const fileUrl = ref('') |
| | | const dialogVisible = ref(false) |
| | | const { proxy } = getCurrentInstance(); |
| | | const javaApi = proxy.javaApi; |
| | | |
| | | // ææ¡£é¢è§ç¶æ |
| | | const isDocShow = ref(true); |
| | | const imgUrl = ref(''); |
| | | const options = ref({ |
| | | xls: false, |
| | | minColLength: 0, |
| | | minRowLength: 0, |
| | | widthOffset: 10, |
| | | heightOffset: 10, |
| | | beforeTransformData: (workbookData) => workbookData, |
| | | transformData: (workbookData) => workbookData, |
| | | }); |
| | | |
| | | // 计ç®å±æ§ - 夿æä»¶ç±»å |
| | | const isImage = computed(() => { |
| | | const state = /\.(jpg|jpeg|png|gif)$/i.test(fileUrl.value); |
| | | if (state) { |
| | | imgUrl.value = fileUrl.value.replaceAll('word', 'img'); |
| | | } |
| | | return state; |
| | | }); |
| | | |
| | | const isPdf = computed(() => { |
| | | console.log(fileUrl.value) |
| | | return /\.pdf$/i.test(fileUrl.value); |
| | | }); |
| | | |
| | | const isDoc = computed(() => { |
| | | return /\.(doc|docx)$/i.test(fileUrl.value); |
| | | }); |
| | | |
| | | const isXls = computed(() => { |
| | | const state = /\.(xls|xlsx)$/i.test(fileUrl.value); |
| | | if (state) { |
| | | options.value.xls = /\.(xls)$/i.test(fileUrl.value); |
| | | } |
| | | return state; |
| | | }); |
| | | |
| | | const isZipOrRar = computed(() => { |
| | | return /\.(zip|rar)$/i.test(fileUrl.value); |
| | | }); |
| | | |
| | | const isSupported = computed(() => { |
| | | return isImage.value || isPdf.value || isDoc.value || isXls.value || isZipOrRar.value; |
| | | }); |
| | | |
| | | // 卿å建aæ ç¾å¹¶è·³è½¬é¢è§PDF |
| | | const previewPdf = (url) => { |
| | | // å建aæ ç¾ |
| | | const link = document.createElement('a'); |
| | | // 设置PDFæä»¶URL |
| | | link.href = url; |
| | | // 卿°æ ç¾é¡µæå¼ |
| | | link.target = '_blank'; |
| | | // å®å
¨å±æ§ï¼é²æ¢æ°é¡µé¢è®¿é®åé¡µé¢ |
| | | link.rel = 'noopener noreferrer'; |
| | | // å¯éï¼è®¾ç½®é¾æ¥ææ¬ |
| | | link.textContent = 'é¢è§PDF'; |
| | | // å°aæ ç¾æ·»å å°é¡µé¢ï¼é¨åæµè§å¨è¦æ±å¿
é¡»å¨DOMä¸ï¼ |
| | | document.body.appendChild(link); |
| | | // 触åç¹å»äºä»¶ |
| | | link.click(); |
| | | // ç§»é¤aæ ç¾ï¼æ¸
çDOM |
| | | document.body.removeChild(link); |
| | | }; |
| | | |
| | | |
| | | // çå¬PDFç¶æååï¼èªå¨è§¦å跳转 |
| | | watch( |
| | | () => isPdf.value, |
| | | (newVal) => { |
| | | |
| | | // å½ç¡®è®¤æ¯PDF䏿件URLæææ¶ |
| | | if (newVal && fileUrl.value) { |
| | | // å
³éå¯¹è¯æ¡ |
| | | dialogVisible.value = false; |
| | | // å 个å°å»¶è¿ç¡®ä¿ç¶ææ´æ°å®æ |
| | | setTimeout(() => { |
| | | previewPdf(fileUrl.value); |
| | | fileUrl.value = ''; |
| | | }, 100); |
| | | } |
| | | } |
| | | ); |
| | | |
| | | // æ¹æ³å®ä¹ |
| | | const renderedHandler = () => { |
| | | console.log("渲æå®æ"); |
| | | isDocShow.value = true; |
| | | resetStyle(); |
| | | }; |
| | | |
| | | const errorHandler = () => { |
| | | console.log("渲æå¤±è´¥"); |
| | | isDocShow.value = false; |
| | | }; |
| | | |
| | | const open = (url) => { |
| | | fileUrl.value = window.location.protocol+'//'+window.location.host+ url; |
| | | dialogVisible.value = true; |
| | | }; |
| | | const handleClose = () => { |
| | | dialogVisible.value = false; |
| | | }; |
| | | |
| | | const resetStyle = () => { |
| | | const elements = document.querySelectorAll('[style*="pt"]'); |
| | | for (const element of elements) { |
| | | const style = element.getAttribute('style'); |
| | | if (style) { |
| | | element.setAttribute('style', style.replace(/pt/g, 'px')); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | // æ´é²openæ¹æ³ä¾å¤é¨è°ç¨ |
| | | defineExpose({ |
| | | open |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | img { |
| | | max-width: 100%; |
| | | display: block; |
| | | margin: 0 auto; |
| | | } |
| | | |
| | | .oneLine { |
| | | overflow: hidden; |
| | | white-space: nowrap; |
| | | text-overflow: ellipsis; |
| | | } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container analytics-container"> |
| | | |
| | | <!-- å
³é®ææ å¡ç --> |
| | | <el-row :gutter="20" class="metrics-cards"> |
| | | <el-col :span="6" v-for="(item, index) in keyMetrics" :key="index"> |
| | | <el-card class="metric-card" :class="item.type"> |
| | | <div class="card-content"> |
| | | <div class="card-icon"> |
| | | <el-icon :size="32"> |
| | | <component :is="item.icon" /> |
| | | </el-icon> |
| | | </div> |
| | | <div class="card-info"> |
| | | <div class="card-number"> |
| | | <el-skeleton-item v-if="loading" variant="text" style="width: 60px; height: 32px;" /> |
| | | <span v-else>{{ item.value }}{{ item.unit }}</span> |
| | | </div> |
| | | <div class="card-label">{{ item.label }}</div> |
| | | <div class="card-trend" :class="item.trend > 0 ? 'positive' : 'negative'"> |
| | | <el-icon> |
| | | <component :is="item.trend > 0 ? 'ArrowUp' : 'ArrowDown'" /> |
| | | </el-icon> |
| | | {{ Math.abs(item.trend) }}% |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- å¾è¡¨åºå --> |
| | | <el-row :gutter="20" class="charts-section"> |
| | | <!-- åå·¥æµå¨çè¶å¿å¾ --> |
| | | <el-col :span="12"> |
| | | <el-card class="chart-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>åå·¥æµå¨çè¶å¿</span> |
| | | <el-tag type="info">è¿12个æ</el-tag> |
| | | </div> |
| | | </template> |
| | | <div class="chart-container"> |
| | | <div ref="turnoverChartRef" class="chart"></div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <!-- é¨é¨äººååå¸ --> |
| | | <el-col :span="12"> |
| | | <el-card class="chart-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>é¨é¨äººååå¸</span> |
| | | <el-tag type="success">å½åç¶æ</el-tag> |
| | | </div> |
| | | </template> |
| | | <div class="chart-container"> |
| | | <div ref="departmentChartRef" class="chart"></div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 第äºè¡å¾è¡¨ --> |
| | | <el-row :gutter="20" class="charts-section"> |
| | | <!-- ç¼å¶è¾¾æç --> |
| | | <el-col :span="12"> |
| | | <el-card class="chart-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>ç¼å¶è¾¾æç</span> |
| | | <el-tag type="warning">åé¨é¨å¯¹æ¯</el-tag> |
| | | </div> |
| | | </template> |
| | | <div class="chart-container"> |
| | | <div ref="staffingChartRef" class="chart"></div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <!-- åå·¥æµå¤±åå åæ --> |
| | | <el-col :span="12"> |
| | | <el-card class="chart-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>åå·¥æµå¤±åå åæ</span> |
| | | <el-tag type="danger">年度ç»è®¡</el-tag> |
| | | </div> |
| | | </template> |
| | | <div class="chart-container"> |
| | | <div ref="attritionChartRef" class="chart"></div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, onUnmounted } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { |
| | | Refresh, |
| | | User, |
| | | TrendCharts, |
| | | DataAnalysis, |
| | | PieChart, |
| | | ArrowUp, |
| | | ArrowDown |
| | | } from '@element-plus/icons-vue' |
| | | import * as echarts from 'echarts' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const loading = ref(false) |
| | | const autoRefreshEnabled = ref(true) |
| | | const autoRefreshInterval = ref(null) |
| | | |
| | | // å¾è¡¨å¼ç¨ |
| | | const turnoverChartRef = ref(null) |
| | | const departmentChartRef = ref(null) |
| | | const staffingChartRef = ref(null) |
| | | const attritionChartRef = ref(null) |
| | | |
| | | // å¾è¡¨å®ä¾ |
| | | let turnoverChart = null |
| | | let departmentChart = null |
| | | let staffingChart = null |
| | | let attritionChart = null |
| | | |
| | | // èªå¨æ´æ°é´éï¼10åéï¼ |
| | | const AUTO_REFRESH_INTERVAL = 10 * 60 * 1000 |
| | | |
| | | // å
³é®ææ æ°æ® |
| | | const keyMetrics = ref([ |
| | | { |
| | | label: 'åå·¥æµå¨ç', |
| | | value: 0, |
| | | unit: '%', |
| | | icon: 'TrendCharts', |
| | | type: 'primary', |
| | | trend: 0 |
| | | }, |
| | | { |
| | | label: 'åå·¥æµå¤±ç', |
| | | value: 0, |
| | | unit: '%', |
| | | icon: 'User', |
| | | type: 'danger', |
| | | trend: 0 |
| | | }, |
| | | { |
| | | label: 'ç¼å¶è¾¾æç', |
| | | value: 0, |
| | | unit: '%', |
| | | icon: 'DataAnalysis', |
| | | type: 'success', |
| | | trend: 0 |
| | | }, |
| | | { |
| | | label: 'å¨èåå·¥æ°', |
| | | value: 0, |
| | | unit: '人', |
| | | icon: 'PieChart', |
| | | type: 'warning', |
| | | trend: 0 |
| | | } |
| | | ]) |
| | | |
| | | // é¨é¨æ°æ® |
| | | const departmentData = ref([]) |
| | | |
| | | // å¯å¨èªå¨å·æ° |
| | | const startAutoRefresh = () => { |
| | | if (autoRefreshInterval.value) { |
| | | clearInterval(autoRefreshInterval.value) |
| | | } |
| | | if (autoRefreshEnabled.value) { |
| | | autoRefreshInterval.value = setInterval(() => { |
| | | refreshData() |
| | | }, AUTO_REFRESH_INTERVAL) |
| | | } |
| | | } |
| | | |
| | | // 忢èªå¨å·æ° |
| | | const stopAutoRefresh = () => { |
| | | if (autoRefreshInterval.value) { |
| | | clearInterval(autoRefreshInterval.value) |
| | | autoRefreshInterval.value = null |
| | | } |
| | | } |
| | | |
| | | // 忢èªå¨å·æ°ç¶æ |
| | | const toggleAutoRefresh = (value) => { |
| | | if (value) { |
| | | startAutoRefresh() |
| | | } else { |
| | | stopAutoRefresh() |
| | | } |
| | | } |
| | | |
| | | // çææ¨¡ææ°æ® |
| | | const generateMockData = () => { |
| | | // çæå
³é®ææ æ°æ® |
| | | keyMetrics.value[0].value = (Math.random() * 5 + 2).toFixed(1) |
| | | keyMetrics.value[0].trend = (Math.random() * 3 - 1.5).toFixed(1) |
| | | |
| | | keyMetrics.value[1].value = (Math.random() * 3 + 1).toFixed(1) |
| | | keyMetrics.value[1].trend = (Math.random() * 2 - 1).toFixed(1) |
| | | |
| | | keyMetrics.value[2].value = (Math.random() * 15 + 85).toFixed(1) |
| | | keyMetrics.value[2].trend = (Math.random() * 3 - 1.5).toFixed(1) |
| | | |
| | | keyMetrics.value[3].value = Math.floor(Math.random() * 50 + 200) |
| | | keyMetrics.value[3].trend = (Math.random() * 2 - 1).toFixed(1) |
| | | |
| | | // çæé¨é¨æ°æ® |
| | | const departments = ['ææ¯é¨', 'éå®é¨', '人äºé¨', 'è´¢å¡é¨', 'ç产é¨', 'å¸åºé¨'] |
| | | departmentData.value = departments.map(dept => ({ |
| | | department: dept, |
| | | currentStaff: Math.floor(Math.random() * 30 + 20), |
| | | plannedStaff: Math.floor(Math.random() * 10 + 35), |
| | | staffingRate: Math.floor(Math.random() * 20 + 80), |
| | | turnoverRate: (Math.random() * 4 + 1).toFixed(1), |
| | | attritionRate: (Math.random() * 2 + 0.5).toFixed(1), |
| | | newHires: Math.floor(Math.random() * 5 + 1), |
| | | resignations: Math.floor(Math.random() * 3 + 1), |
| | | status: Math.random() > 0.7 ? 'å¼å¸¸' : 'æ£å¸¸' |
| | | })) |
| | | } |
| | | |
| | | // å·æ°æ°æ® |
| | | const refreshData = async () => { |
| | | loading.value = true |
| | | try { |
| | | // 模æAPIè°ç¨å»¶è¿ |
| | | await new Promise(resolve => setTimeout(resolve, 500)) |
| | | |
| | | generateMockData() |
| | | renderAllCharts() |
| | | |
| | | if (!autoRefreshEnabled.value) { |
| | | ElMessage.success('æ°æ®å·æ°æå') |
| | | } |
| | | } catch (error) { |
| | | console.error('å·æ°æ°æ®å¤±è´¥:', error) |
| | | ElMessage.error('å·æ°æ°æ®å¤±è´¥') |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | // åå§åå¾è¡¨ |
| | | const initCharts = () => { |
| | | setTimeout(() => { |
| | | if (turnoverChartRef.value) { |
| | | turnoverChart = echarts.init(turnoverChartRef.value) |
| | | } |
| | | if (departmentChartRef.value) { |
| | | departmentChart = echarts.init(departmentChartRef.value) |
| | | } |
| | | if (staffingChartRef.value) { |
| | | staffingChart = echarts.init(staffingChartRef.value) |
| | | } |
| | | if (attritionChartRef.value) { |
| | | attritionChart = echarts.init(attritionChartRef.value) |
| | | } |
| | | |
| | | renderAllCharts() |
| | | }, 300) |
| | | } |
| | | |
| | | // æ¸²æææå¾è¡¨ |
| | | const renderAllCharts = () => { |
| | | renderTurnoverChart() |
| | | renderDepartmentChart() |
| | | renderStaffingChart() |
| | | renderAttritionChart() |
| | | } |
| | | |
| | | // 渲æåå·¥æµå¨çè¶å¿å¾ |
| | | const renderTurnoverChart = () => { |
| | | if (!turnoverChart) return |
| | | |
| | | const months = ['1æ', '2æ', '3æ', '4æ', '5æ', '6æ', '7æ', '8æ', '9æ', '10æ', '11æ', '12æ'] |
| | | const turnoverData = months.map(() => (Math.random() * 5 + 2).toFixed(1)) |
| | | const attritionData = months.map(() => (Math.random() * 3 + 1).toFixed(1)) |
| | | |
| | | const option = { |
| | | title: { |
| | | text: 'åå·¥æµå¨çè¶å¿', |
| | | left: 'center', |
| | | textStyle: { fontSize: 16, fontWeight: 'normal' } |
| | | }, |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | axisPointer: { type: 'cross' } |
| | | }, |
| | | legend: { |
| | | data: ['æµå¨ç', 'æµå¤±ç'], |
| | | bottom: 10 |
| | | }, |
| | | grid: { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '15%', |
| | | top: '15%', |
| | | containLabel: true |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: months, |
| | | boundaryGap: false |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | axisLabel: { formatter: '{value}%' } |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'æµå¨ç', |
| | | type: 'line', |
| | | data: turnoverData, |
| | | smooth: true, |
| | | lineStyle: { color: '#409EFF' }, |
| | | itemStyle: { color: '#409EFF' } |
| | | }, |
| | | { |
| | | name: 'æµå¤±ç', |
| | | type: 'line', |
| | | data: attritionData, |
| | | smooth: true, |
| | | lineStyle: { color: '#F56C6C' }, |
| | | itemStyle: { color: '#F56C6C' } |
| | | } |
| | | ] |
| | | } |
| | | |
| | | turnoverChart.setOption(option) |
| | | } |
| | | |
| | | // 渲æé¨é¨äººååå¸å¾ |
| | | const renderDepartmentChart = () => { |
| | | if (!departmentChart) return |
| | | |
| | | const data = departmentData.value.map(item => ({ |
| | | name: item.department, |
| | | value: item.currentStaff |
| | | })) |
| | | |
| | | const option = { |
| | | title: { |
| | | text: 'é¨é¨äººååå¸', |
| | | left: 'center', |
| | | textStyle: { fontSize: 16, fontWeight: 'normal' } |
| | | }, |
| | | tooltip: { |
| | | trigger: 'item', |
| | | formatter: '{a} <br/>{b}: {c}人 ({d}%)' |
| | | }, |
| | | legend: { |
| | | orient: 'vertical', |
| | | left: 'left', |
| | | top: 'middle' |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'äººåæ°é', |
| | | type: 'pie', |
| | | radius: ['40%', '70%'], |
| | | center: ['60%', '50%'], |
| | | data: data, |
| | | emphasis: { |
| | | itemStyle: { |
| | | shadowBlur: 10, |
| | | shadowOffsetX: 0, |
| | | shadowColor: 'rgba(0, 0, 0, 0.5)' |
| | | } |
| | | } |
| | | } |
| | | ] |
| | | } |
| | | |
| | | departmentChart.setOption(option) |
| | | } |
| | | |
| | | // 渲æç¼å¶è¾¾æçå¾ |
| | | const renderStaffingChart = () => { |
| | | if (!staffingChart) return |
| | | |
| | | const departments = departmentData.value.map(item => item.department) |
| | | const rates = departmentData.value.map(item => item.staffingRate) |
| | | |
| | | const option = { |
| | | title: { |
| | | text: 'ç¼å¶è¾¾æç', |
| | | left: 'center', |
| | | textStyle: { fontSize: 16, fontWeight: 'normal' } |
| | | }, |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | axisPointer: { type: 'shadow' } |
| | | }, |
| | | grid: { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '15%', |
| | | top: '15%', |
| | | containLabel: true |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: departments, |
| | | axisLabel: { rotate: 45 } |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | axisLabel: { formatter: '{value}%' }, |
| | | max: 100 |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'è¾¾æç', |
| | | type: 'bar', |
| | | data: rates, |
| | | itemStyle: { |
| | | color: function(params) { |
| | | const value = params.value |
| | | if (value >= 90) return '#67C23A' |
| | | if (value >= 80) return '#E6A23C' |
| | | return '#F56C6C' |
| | | } |
| | | } |
| | | } |
| | | ] |
| | | } |
| | | |
| | | staffingChart.setOption(option) |
| | | } |
| | | |
| | | // 渲æåå·¥æµå¤±åå åæå¾ |
| | | const renderAttritionChart = () => { |
| | | if (!attritionChart) return |
| | | |
| | | const reasons = ['èªèµå¾
é', 'èä¸åå±', 'å·¥ä½ç¯å¢', '个人åå ', 'å
¶ä»'] |
| | | const data = reasons.map(() => Math.floor(Math.random() * 20 + 5)) |
| | | |
| | | const option = { |
| | | title: { |
| | | text: 'åå·¥æµå¤±åå åæ', |
| | | left: 'center', |
| | | textStyle: { fontSize: 16, fontWeight: 'normal' } |
| | | }, |
| | | tooltip: { |
| | | trigger: 'item', |
| | | formatter: '{a} <br/>{b}: {c}人 ({d}%)' |
| | | }, |
| | | legend: { |
| | | orient: 'vertical', |
| | | left: 'left', |
| | | top: 'middle' |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'æµå¤±äººæ°', |
| | | type: 'pie', |
| | | radius: '50%', |
| | | center: ['60%', '50%'], |
| | | data: reasons.map((reason, index) => ({ |
| | | name: reason, |
| | | value: data[index] |
| | | })), |
| | | emphasis: { |
| | | itemStyle: { |
| | | shadowBlur: 10, |
| | | shadowOffsetX: 0, |
| | | shadowColor: 'rgba(0, 0, 0, 0.5)' |
| | | } |
| | | } |
| | | } |
| | | ] |
| | | } |
| | | |
| | | attritionChart.setOption(option) |
| | | } |
| | | |
| | | // çå½å¨æ |
| | | onMounted(() => { |
| | | generateMockData() |
| | | initCharts() |
| | | startAutoRefresh() |
| | | }) |
| | | |
| | | onUnmounted(() => { |
| | | stopAutoRefresh() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .analytics-container { |
| | | padding: 20px; |
| | | background-color: #f5f7fa; |
| | | min-height: 100vh; |
| | | } |
| | | |
| | | .page-header { |
| | | text-align: center; |
| | | margin-bottom: 30px; |
| | | padding: 20px; |
| | | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | | border-radius: 12px; |
| | | color: white; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | color: white; |
| | | margin-bottom: 10px; |
| | | font-size: 28px; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .page-header p { |
| | | color: rgba(255, 255, 255, 0.9); |
| | | font-size: 14px; |
| | | margin: 0 0 15px 0; |
| | | } |
| | | |
| | | .header-controls { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .refresh-btn { |
| | | margin-left: 20px; |
| | | } |
| | | |
| | | .metrics-cards { |
| | | margin-bottom: 30px; |
| | | } |
| | | |
| | | .metric-card { |
| | | border-radius: 12px; |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
| | | transition: all 0.3s ease; |
| | | border: none; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .metric-card:hover { |
| | | transform: translateY(-5px); |
| | | box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); |
| | | } |
| | | |
| | | .metric-card.primary { |
| | | border-left: 4px solid #409EFF; |
| | | background: linear-gradient(135deg, #409EFF 0%, #36a3f7 100%); |
| | | } |
| | | |
| | | .metric-card.danger { |
| | | border-left: 4px solid #F56C6C; |
| | | background: linear-gradient(135deg, #F56C6C 0%, #f78989 100%); |
| | | } |
| | | |
| | | .metric-card.success { |
| | | border-left: 4px solid #67C23A; |
| | | background: linear-gradient(135deg, #67C23A 0%, #85ce61 100%); |
| | | } |
| | | |
| | | .metric-card.warning { |
| | | border-left: 4px solid #E6A23C; |
| | | background: linear-gradient(135deg, #E6A23C 0%, #ebb563 100%); |
| | | } |
| | | |
| | | .card-content { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 20px; |
| | | } |
| | | |
| | | .card-icon { |
| | | margin-right: 20px; |
| | | color: white; |
| | | } |
| | | |
| | | .card-info { |
| | | flex: 1; |
| | | } |
| | | |
| | | .card-number { |
| | | font-size: 32px; |
| | | font-weight: 600; |
| | | color: white; |
| | | margin-bottom: 5px; |
| | | } |
| | | |
| | | .card-label { |
| | | font-size: 14px; |
| | | color: rgba(255, 255, 255, 0.9); |
| | | margin-bottom: 5px; |
| | | } |
| | | |
| | | .card-trend { |
| | | font-size: 12px; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .card-trend.positive { |
| | | color: #67C23A; |
| | | } |
| | | |
| | | .card-trend.negative { |
| | | color: #F56C6C; |
| | | } |
| | | |
| | | .charts-section { |
| | | margin-bottom: 30px; |
| | | } |
| | | |
| | | .chart-card { |
| | | border-radius: 12px; |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
| | | border: none; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | padding: 15px 20px; |
| | | border-bottom: 1px solid #ebeef5; |
| | | } |
| | | |
| | | .chart-container { |
| | | height: 350px; |
| | | padding: 20px; |
| | | } |
| | | |
| | | .chart { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | |
| | | /* ååºå¼è®¾è®¡ */ |
| | | @media (max-width: 768px) { |
| | | .analytics-container { |
| | | padding: 10px; |
| | | } |
| | | |
| | | .page-header { |
| | | padding: 15px; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | font-size: 24px; |
| | | } |
| | | |
| | | .header-controls { |
| | | flex-direction: column; |
| | | gap: 15px; |
| | | } |
| | | |
| | | .refresh-btn { |
| | | margin-left: 0; |
| | | } |
| | | |
| | | .metrics-cards .el-col { |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .charts-section .el-col { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .chart-container { |
| | | height: 300px; |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 480px) { |
| | | .page-header h2 { |
| | | font-size: 20px; |
| | | } |
| | | |
| | | .card-number { |
| | | font-size: 24px; |
| | | } |
| | | |
| | | .chart-container { |
| | | height: 250px; |
| | | } |
| | | } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog |
| | | v-model="dialogFormVisible" |
| | | title="详æ
" |
| | | width="70%" |
| | | @close="closeDia" |
| | | > |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :tableLoading="tableLoading" |
| | | height="600" |
| | | ></PIMTable> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {ref} from "vue"; |
| | | import {staffOnJobInfo} from "@/api/personnelManagement/employeeRecord.js"; |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits(['close']) |
| | | |
| | | const dialogFormVisible = ref(false); |
| | | const operationType = ref('') |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "ååå¹´é", |
| | | prop: "contractTerm", |
| | | }, |
| | | { |
| | | label: "ååå¼å§æ¥æ", |
| | | prop: "contractStartTime", |
| | | }, |
| | | { |
| | | label: "ååç»ææ¥æ", |
| | | prop: "contractEndTime", |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const tableLoading = ref(false); |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = (type, row) => { |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | if (operationType.value === 'edit') { |
| | | staffOnJobInfo({staffNo: row.staffNo}).then(res => { |
| | | tableData.value = res.data |
| | | }) |
| | | } |
| | | } |
| | | |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | dialogFormVisible.value = false; |
| | | emit('close') |
| | | }; |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog |
| | | v-model="dialogFormVisible" |
| | | title="ä¸ä¼ éä»¶" |
| | | width="50%" |
| | | @close="closeDia" |
| | | > |
| | | <div style="margin-bottom: 10px;text-align: right"> |
| | | <el-upload |
| | | v-model:file-list="fileList" |
| | | class="upload-demo" |
| | | :action="uploadUrl" |
| | | :on-success="handleUploadSuccess" |
| | | :on-error="handleUploadError" |
| | | name="file" |
| | | :show-file-list="false" |
| | | :headers="headers" |
| | | style="display: inline;margin-right: 10px" |
| | | > |
| | | <el-button type="primary">ä¸ä¼ éä»¶</el-button> |
| | | </el-upload> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :tableLoading="tableLoading" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | height="500" |
| | | > |
| | | </PIMTable> |
| | | <pagination |
| | | style="margin: 10px 0" |
| | | v-show="total > 0" |
| | | @pagination="paginationSearch" |
| | | :total="total" |
| | | :page="page.current" |
| | | :limit="page.size" |
| | | /> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <filePreview ref="filePreviewRef" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {ref} from "vue"; |
| | | import {ElMessageBox} from "element-plus"; |
| | | import {getToken} from "@/utils/auth.js"; |
| | | import filePreview from '@/components/filePreview/index.vue' |
| | | import { |
| | | fileAdd, |
| | | fileDel, |
| | | fileListPage |
| | | } from "@/api/financialManagement/revenueManagement.js"; |
| | | import Pagination from "@/components/PIMTable/Pagination.vue"; |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits(['close']) |
| | | |
| | | const dialogFormVisible = ref(false); |
| | | const currentId = ref('') |
| | | const selectedRows = ref([]); |
| | | const filePreviewRef = ref() |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "æä»¶åç§°", |
| | | prop: "name", |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | operation: [ |
| | | { |
| | | name: "ä¸è½½", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | downLoadFile(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "é¢è§", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | lookFile(row); |
| | | }, |
| | | } |
| | | ], |
| | | }, |
| | | ]); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }); |
| | | const total = ref(0); |
| | | const tableData = ref([]); |
| | | const fileList = ref([]); |
| | | const tableLoading = ref(false); |
| | | const accountType = ref('') |
| | | const headers = ref({ |
| | | Authorization: "Bearer " + getToken(), |
| | | }); |
| | | const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/file/upload"); // ä¸ä¼ çå¾çæå¡å¨å°å |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = (row,type) => { |
| | | accountType.value = type; |
| | | dialogFormVisible.value = true; |
| | | currentId.value = row.id; |
| | | getList() |
| | | } |
| | | const paginationSearch = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | fileListPage({accountId: currentId.value,accountType:accountType.value, ...page}).then(res => { |
| | | tableData.value = res.data.records; |
| | | total.value = res.data.total; |
| | | }) |
| | | } |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | dialogFormVisible.value = false; |
| | | emit('close') |
| | | }; |
| | | // ä¸ä¼ æåå¤ç |
| | | function handleUploadSuccess(res, file) { |
| | | // 妿ä¸ä¼ æå |
| | | if (res.code == 200) { |
| | | const fileRow = {} |
| | | fileRow.name = res.data.originalName |
| | | fileRow.url = res.data.tempPath |
| | | uploadFile(fileRow) |
| | | } else { |
| | | proxy.$modal.msgError("æä»¶ä¸ä¼ 失败"); |
| | | } |
| | | } |
| | | function uploadFile(file) { |
| | | file.accountId = currentId.value; |
| | | file.accountType = accountType.value; |
| | | fileAdd(file).then(res => { |
| | | proxy.$modal.msgSuccess("æä»¶ä¸ä¼ æå"); |
| | | getList() |
| | | }) |
| | | } |
| | | // ä¸ä¼ 失败å¤ç |
| | | function handleUploadError() { |
| | | proxy.$modal.msgError("æä»¶ä¸ä¼ 失败"); |
| | | } |
| | | // ä¸è½½éä»¶ |
| | | const downLoadFile = (row) => { |
| | | proxy.$download.name(row.url); |
| | | } |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | ids = selectedRows.value.map((item) => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | fileDel(ids).then((res) => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }); |
| | | }).catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | // é¢è§éä»¶ |
| | | const lookFile = (row) => { |
| | | filePreviewRef.value.open(row.url) |
| | | } |
| | | |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">å§åï¼</span> |
| | | <el-input v-model="searchForm.staffName" style="width: 240px" placeholder="请è¾å
¥å§åæç´¢" @change="handleQuery" |
| | | clearable :prefix-icon="Search" /> |
| | | <span style="margin-left: 10px" class="search_title">ååç»ææ¥æï¼</span> |
| | | <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" |
| | | placeholder="è¯·éæ©" clearable @change="changeDaterange" /> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px">æç´¢</el-button> |
| | | </div> |
| | | <div style="margin: 10PX 0;"> |
| | | <!-- <el-button type="primary" @click="openForm('add')">æ°å¢å
¥è</el-button>--> |
| | | <!-- <el-button type="info" @click="handleImport">导å
¥</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" :tableData="tableData" :page="page" :isSelection="true" |
| | | @selection-change="handleSelectionChange" :tableLoading="tableLoading" @pagination="pagination" |
| | | :total="page.total"></PIMTable> |
| | | </div> |
| | | <form-dia ref="formDia" @close="handleQuery"></form-dia> |
| | | |
| | | <!-- åå导å
¥å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | :title="upload.title" |
| | | v-model="upload.open" |
| | | width="400px" |
| | | append-to-body |
| | | > |
| | | <el-upload |
| | | ref="uploadRef" |
| | | :limit="1" |
| | | accept=".xlsx, .xls" |
| | | :headers="upload.headers" |
| | | :action="upload.url + '?updateSupport=' + upload.updateSupport" |
| | | :disabled="upload.isUploading" |
| | | :on-progress="handleFileUploadProgress" |
| | | :on-success="handleFileSuccess" |
| | | :auto-upload="false" |
| | | drag |
| | | > |
| | | <el-icon class="el-icon--upload"><upload-filled /></el-icon> |
| | | <div class="el-upload__text">å°æä»¶æå°æ¤å¤ï¼æ<em>ç¹å»ä¸ä¼ </em></div> |
| | | <template #tip> |
| | | <div class="el-upload__tip text-center"> |
| | | <span>ä»
å
许导å
¥xlsãxlsxæ ¼å¼æä»¶ã</span> |
| | | <!-- <el-link |
| | | type="primary" |
| | | :underline="false" |
| | | style="font-size: 12px; vertical-align: baseline" |
| | | @click="importTemplate" |
| | | >ä¸è½½æ¨¡æ¿</el-link |
| | | > --> |
| | | </div> |
| | | </template> |
| | | </el-upload> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitFileForm">ç¡® å®</el-button> |
| | | <el-button @click="upload.open = false">å æ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <files-dia ref="filesDia"></files-dia> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { onMounted, ref } from "vue"; |
| | | import FormDia from "@/views/personnelManagement/contractManagement/components/formDia.vue"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { staffOnJobListPage } from "@/api/personnelManagement/employeeRecord.js"; |
| | | import dayjs from "dayjs"; |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | import { getToken } from "@/utils/auth.js"; |
| | | import FilesDia from "./filesDia.vue"; |
| | | const data = reactive({ |
| | | searchForm: { |
| | | staffName: "", |
| | | entryDate: [ |
| | | dayjs().format("YYYY-MM-DD"), |
| | | dayjs().add(1, "day").format("YYYY-MM-DD"), |
| | | ], // å½å
¥æ¥æ |
| | | entryDateStart: dayjs().format("YYYY-MM-DD"), |
| | | entryDateEnd: dayjs().add(1, "day").format("YYYY-MM-DD"), |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "ç¶æ", |
| | | prop: "staffState", |
| | | dataType: "tag", |
| | | formatData: (params) => { |
| | | if (params == 0) { |
| | | return "离è"; |
| | | } else if (params == 1) { |
| | | return "å¨è"; |
| | | } else { |
| | | return null; |
| | | } |
| | | }, |
| | | formatType: (params) => { |
| | | if (params == 0) { |
| | | return "danger"; |
| | | } else if (params == 1) { |
| | | return "primary"; |
| | | } else { |
| | | return null; |
| | | } |
| | | }, |
| | | }, |
| | | { |
| | | label: "åå·¥ç¼å·", |
| | | prop: "staffNo", |
| | | }, |
| | | { |
| | | label: "å§å", |
| | | prop: "staffName", |
| | | }, |
| | | { |
| | | label: "æ§å«", |
| | | prop: "sex", |
| | | }, |
| | | { |
| | | label: "ç±è´¯", |
| | | prop: "nativePlace", |
| | | }, |
| | | { |
| | | label: "å²ä½", |
| | | prop: "postJob", |
| | | }, |
| | | { |
| | | label: "å®¶åºä½å", |
| | | prop: "adress", |
| | | width: 200 |
| | | }, |
| | | { |
| | | label: "第ä¸å¦å", |
| | | prop: "firstStudy", |
| | | }, |
| | | { |
| | | label: "ä¸ä¸", |
| | | prop: "profession", |
| | | width: 100 |
| | | }, |
| | | { |
| | | label: "身份è¯å·", |
| | | prop: "identityCard", |
| | | width: 200 |
| | | }, |
| | | { |
| | | label: "å¹´é¾", |
| | | prop: "age", |
| | | }, |
| | | { |
| | | label: "èç³»çµè¯", |
| | | prop: "phone", |
| | | width: 150 |
| | | }, |
| | | { |
| | | label: "ç´§æ¥è系人", |
| | | prop: "emergencyContact", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "ç´§æ¥è系人çµè¯", |
| | | prop: "emergencyContactPhone", |
| | | width: 150 |
| | | }, |
| | | { |
| | | label: "ååå¹´é", |
| | | prop: "contractTerm", |
| | | }, |
| | | // { |
| | | // label: "ååå¼å§æ¥æ", |
| | | // prop: "contractStartTime", |
| | | // width: 120 |
| | | // }, |
| | | { |
| | | label: "ååç»ææ¥æ", |
| | | prop: "contractExpireTime", |
| | | width: 120 |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: 'right', |
| | | width: 120, |
| | | operation: [ |
| | | { |
| | | name: "详æ
", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "éä»¶", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openFilesFormDia(row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | const filesDia = ref() |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | total: 0, |
| | | }); |
| | | const formDia = ref() |
| | | const { proxy } = getCurrentInstance() |
| | | |
| | | const changeDaterange = (value) => { |
| | | searchForm.value.entryDateStart = undefined; |
| | | searchForm.value.entryDateEnd = undefined; |
| | | if (value) { |
| | | searchForm.value.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD"); |
| | | searchForm.value.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD"); |
| | | } |
| | | getList(); |
| | | }; |
| | | // æå¼éä»¶å¼¹æ¡ |
| | | const openFilesFormDia = (row) => { |
| | | console.log(row) |
| | | nextTick(() => { |
| | | filesDia.value?.openDialog( row,'åå') |
| | | }) |
| | | }; |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const pagination = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | const params = { ...searchForm.value, ...page }; |
| | | params.entryDate = undefined |
| | | staffOnJobListPage(params).then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records |
| | | page.total = res.data.total; |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | }) |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = (type, row) => { |
| | | nextTick(() => { |
| | | formDia.value?.openDialog(type, row) |
| | | }) |
| | | }; |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/staff/staffOnJob/export", {}, "åå管ç.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | const upload = reactive({ |
| | | // æ¯å¦æ¾ç¤ºå¼¹åºå±ï¼åå导å
¥ï¼ |
| | | open: false, |
| | | // å¼¹åºå±æ é¢ï¼åå导å
¥ï¼ |
| | | title: "", |
| | | // æ¯å¦ç¦ç¨ä¸ä¼ |
| | | isUploading: false, |
| | | // æ¯å¦æ´æ°å·²ç»åå¨çç¨æ·æ°æ® |
| | | updateSupport: 1, |
| | | // 设置ä¸ä¼ ç请æ±å¤´é¨ |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | // ä¸ä¼ çå°å |
| | | url: import.meta.env.VITE_APP_BASE_API + "/staff/staffOnJob/import", |
| | | }); |
| | | /** 导å
¥æé®æä½ */ |
| | | function handleImport() { |
| | | upload.title = "åå导å
¥"; |
| | | upload.open = true; |
| | | } |
| | | /** æäº¤ä¸ä¼ æä»¶ */ |
| | | function submitFileForm() { |
| | | console.log(upload.url + '?updateSupport=' + upload.updateSupport) |
| | | proxy.$refs["uploadRef"].submit(); |
| | | } |
| | | /**æä»¶ä¸ä¼ ä¸å¤ç */ |
| | | const handleFileUploadProgress = (event, file, fileList) => { |
| | | upload.isUploading = true; |
| | | }; |
| | | /** æä»¶ä¸ä¼ æåå¤ç */ |
| | | const handleFileSuccess = (response, file, fileList) => { |
| | | upload.open = false; |
| | | upload.isUploading = false; |
| | | proxy.$refs["uploadRef"].handleRemove(file); |
| | | getList(); |
| | | }; |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped></style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog |
| | | v-model="dialogFormVisible" |
| | | :title="operationType === 'add' ? 'æ°å¢ç¦»è' : 'ç¼è¾ç¦»è'" |
| | | width="70%" |
| | | @close="closeDia" |
| | | > |
| | | <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef"> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å§åï¼" prop="staffName"> |
| | | <!-- <el-input v-model="form.staffName" placeholder="请è¾å
¥" clearable/> --> |
| | | <el-select v-model="form.staffName" placeholder="è¯·éæ©äººå" style="width: 100%" @change="handleSelect"> |
| | | <el-option |
| | | v-for="item in personList" |
| | | :key="item.id" |
| | | :label="item.staffName" |
| | | :value="item.staffName" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åå·¥ç¼å·ï¼" prop="staffNo"> |
| | | <el-input v-model="form.staffNo" placeholder="请è¾å
¥" clearable disabled/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ§å«ï¼" prop="sex"> |
| | | <el-select v-model="form.sex" disabled> |
| | | <el-option label="ç·" value="ç·" /> |
| | | <el-option label="女" value="女" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç±è´¯ï¼" prop="nativePlace"> |
| | | <el-input v-model="form.nativePlace" placeholder="请è¾å
¥" clearable disabled/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å²ä½ï¼" prop="postJob"> |
| | | <el-input v-model="form.postJob" placeholder="请è¾å
¥" clearable disabled/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å®¶åºä½åï¼" prop="adress"> |
| | | <el-input v-model="form.adress" placeholder="请è¾å
¥" clearable disabled/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="第ä¸å¦åï¼" prop="firstStudy"> |
| | | <el-input v-model="form.firstStudy" placeholder="请è¾å
¥" clearable disabled/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¸ä¸ï¼" prop="profession"> |
| | | <el-input v-model="form.profession" placeholder="请è¾å
¥" clearable disabled/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="身份è¯å·ï¼" prop="identityCard"> |
| | | <el-input v-model="form.identityCard" placeholder="请è¾å
¥" clearable disabled/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¹´é¾ï¼" prop="age"> |
| | | <el-input-number v-model="form.age" :precision="0" :step="1" style="width: 100%" disabled/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èç³»çµè¯ï¼" prop="phone"> |
| | | <el-input v-model="form.phone" placeholder="请è¾å
¥" clearable disabled/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç´§æ¥è系人ï¼" prop="emergencyContact"> |
| | | <el-input v-model="form.emergencyContact" placeholder="请è¾å
¥" clearable disabled/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç´§æ¥è系人èç³»çµè¯ï¼" prop="emergencyContactPhone"> |
| | | <el-input v-model="form.emergencyContactPhone" placeholder="请è¾å
¥" clearable disabled/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ååå¹´éï¼" prop="contractTerm"> |
| | | <el-input-number v-model="form.contractTerm" :precision="0" :step="1" style="width: 100%" disabled/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ååå¼å§æ¥æï¼" prop="contractStartTime"> |
| | | <el-date-picker |
| | | disabled |
| | | v-model="form.contractStartTime" |
| | | 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="contractEndTime"> |
| | | <el-date-picker |
| | | disabled |
| | | v-model="form.contractEndTime" |
| | | type="date" |
| | | placeholder="è¯·éæ©æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | clearable |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitForm">确认</el-button> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {ref} from "vue"; |
| | | import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate,getStaffOnJob} from "@/api/personnelManagement/onboarding.js"; |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits(['close']) |
| | | |
| | | const dialogFormVisible = ref(false); |
| | | const operationType = ref('') |
| | | const data = reactive({ |
| | | form: { |
| | | staffNo: "", |
| | | staffName: "", |
| | | sex: "", |
| | | nativePlace: "", |
| | | postJob: "", |
| | | adress: "", |
| | | firstStudy: "", |
| | | profession: "", |
| | | identityCard: "", |
| | | age: 0, |
| | | phone: "", |
| | | emergencyContact: "", |
| | | emergencyContactPhone: "", |
| | | contractTerm: 0, |
| | | contractStartTime: "", |
| | | contractEndTime: "", |
| | | staffState: "", |
| | | }, |
| | | rules: { |
| | | staffNo: [{ required: true, message: "请è¾å
¥", trigger: "blur" },], |
| | | staffName: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | sex: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | nativePlace: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | postJob: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | adress: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | firstStudy: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | profession: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | identityCard: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | age: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | phone: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | emergencyContact: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | emergencyContactPhone: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | contractTerm: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | contractStartTime: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | contractEndTime: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | }, |
| | | }); |
| | | const { form, rules } = toRefs(data); |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = (type, row) => { |
| | | getList() |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | if (operationType.value === 'edit') { |
| | | getStaffJoinInfo(row.id).then(res => { |
| | | form.value = {...res.data} |
| | | }) |
| | | } |
| | | } |
| | | // æäº¤äº§å表å |
| | | const submitForm = () => { |
| | | proxy.$refs.formRef.validate(valid => { |
| | | if (valid) { |
| | | form.value.staffState = 0 |
| | | if (operationType.value === "add") { |
| | | staffJoinAdd(form.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | }) |
| | | } else { |
| | | staffJoinUpdate(form.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | }) |
| | | } |
| | | } |
| | | }) |
| | | } |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | | dialogFormVisible.value = false; |
| | | emit('close') |
| | | }; |
| | | |
| | | const personList = ref([]); |
| | | |
| | | /** |
| | | * è·åå½åå¨è人åå表 |
| | | */ |
| | | const getList = () => { |
| | | getStaffOnJob().then(res => { |
| | | personList.value = res.data |
| | | }) |
| | | }; |
| | | |
| | | const handleSelect = (val) => { |
| | | let obj = personList.value.find(item => item.staffName === val) |
| | | let { |
| | | sex, |
| | | phone, |
| | | staffNo, |
| | | nativePlace, |
| | | postJob, |
| | | adress, |
| | | firstStudy, |
| | | profession, |
| | | identityCard, |
| | | age, |
| | | emergencyContact, |
| | | emergencyContactPhone, |
| | | contractTerm, |
| | | contractStartTime, |
| | | contractEndTime, |
| | | staffName |
| | | } = obj |
| | | form.value = { |
| | | sex, |
| | | phone, |
| | | staffNo, |
| | | nativePlace, |
| | | postJob, |
| | | adress, |
| | | firstStudy, |
| | | profession, |
| | | identityCard, |
| | | age, |
| | | emergencyContact, |
| | | emergencyContactPhone, |
| | | contractTerm, |
| | | contractStartTime, |
| | | contractEndTime, |
| | | staffName |
| | | } |
| | | |
| | | } |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">å§åï¼</span> |
| | | <el-input |
| | | v-model="searchForm.staffName" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥å§åæç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | /> |
| | | <span style="margin-left: 10px;" class="search_title">ååå¼å§æ¥æï¼</span> |
| | | <el-date-picker |
| | | v-model="searchForm.entryDateStart" |
| | | type="date" |
| | | placeholder="è¯·éæ©ååå¼å§æ¥æ" |
| | | size="default" |
| | | @change="(date) => handleDateChange(date,1)" |
| | | /> |
| | | <span style="margin-left: 10px;" class="search_title">ååç»ææ¥æï¼</span> |
| | | <el-date-picker |
| | | v-model="searchForm.entryDateEnd" |
| | | type="date" |
| | | placeholder="è¯·éæ©ååç»ææ¥æ" |
| | | size="default" |
| | | @change="(date) => handleDateChange(date,2)" |
| | | /> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px" |
| | | >æç´¢</el-button |
| | | > |
| | | </div> |
| | | <div style="margin: 10PX 0;"> |
| | | <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> |
| | | <div class="table_list"> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total" |
| | | ></PIMTable> |
| | | </div> |
| | | <form-dia ref="formDia" @close="handleQuery"></form-dia> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import {onMounted, ref} from "vue"; |
| | | import FormDia from "@/views/personnelManagement/dimission/components/formDia.vue"; |
| | | import {staffJoinDel, staffJoinListPage} from "@/api/personnelManagement/onboarding.js"; |
| | | import {ElMessageBox} from "element-plus"; |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | | staffName: "", |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "ç¶æ", |
| | | prop: "staffState", |
| | | dataType: "tag", |
| | | formatData: (params) => { |
| | | if (params == 0) { |
| | | return "离è"; |
| | | } else if (params == 1) { |
| | | return "å¨è"; |
| | | } else { |
| | | return null; |
| | | } |
| | | }, |
| | | formatType: (params) => { |
| | | if (params == 0) { |
| | | return "danger"; |
| | | } else if (params == 1) { |
| | | return "primary"; |
| | | } else { |
| | | return null; |
| | | } |
| | | }, |
| | | }, |
| | | { |
| | | label: "åå·¥ç¼å·", |
| | | prop: "staffNo", |
| | | }, |
| | | { |
| | | label: "å§å", |
| | | prop: "staffName", |
| | | }, |
| | | { |
| | | label: "æ§å«", |
| | | prop: "sex", |
| | | }, |
| | | { |
| | | label: "ç±è´¯", |
| | | prop: "nativePlace", |
| | | }, |
| | | { |
| | | label: "å²ä½", |
| | | prop: "postJob", |
| | | }, |
| | | { |
| | | label: "å®¶åºä½å", |
| | | prop: "adress", |
| | | width:200 |
| | | }, |
| | | { |
| | | label: "第ä¸å¦å", |
| | | prop: "firstStudy", |
| | | }, |
| | | { |
| | | label: "ä¸ä¸", |
| | | prop: "profession", |
| | | width:100 |
| | | }, |
| | | { |
| | | label: "身份è¯å·", |
| | | prop: "identityCard", |
| | | width:200 |
| | | }, |
| | | { |
| | | label: "å¹´é¾", |
| | | prop: "age", |
| | | }, |
| | | { |
| | | label: "èç³»çµè¯", |
| | | prop: "phone", |
| | | width:150 |
| | | }, |
| | | { |
| | | label: "ç´§æ¥è系人", |
| | | prop: "emergencyContact", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "ç´§æ¥è系人çµè¯", |
| | | prop: "emergencyContactPhone", |
| | | width:150 |
| | | }, |
| | | { |
| | | label: "ååå¹´é", |
| | | prop: "contractTerm", |
| | | }, |
| | | { |
| | | label: "ååå¼å§æ¥æ", |
| | | prop: "contractStartTime", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "ååç»ææ¥æ", |
| | | prop: "contractEndTime", |
| | | width: 120 |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | total: 0, |
| | | }); |
| | | const formDia = ref() |
| | | const { proxy } = getCurrentInstance() |
| | | |
| | | |
| | | const handleDateChange = (value,type) => { |
| | | searchForm.value.entryDateEnd = null |
| | | searchForm.value.entryDateStart = null |
| | | if(type === 1){ |
| | | if (value) { |
| | | searchForm.value.entryDateStart = dayjs(value).format("YYYY-MM-DD"); |
| | | } |
| | | }else{ |
| | | if (value) { |
| | | searchForm.value.entryDateEnd = dayjs(value).format("YYYY-MM-DD"); |
| | | } |
| | | } |
| | | getList(); |
| | | }; |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const pagination = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | staffJoinListPage({...page, ...searchForm.value, staffState: 0}).then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records |
| | | page.total = res.data.total; |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | }) |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = (type, row) => { |
| | | nextTick(() => { |
| | | formDia.value?.openDialog(type, row) |
| | | }) |
| | | }; |
| | | |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | ids = selectedRows.value.map((item) => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | staffJoinDel(ids).then((res) => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/staff/staffJoinLeaveRecord/export", {staffState: 0}, "人å离è.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped></style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog |
| | | v-model="dialogFormVisible" |
| | | title="详æ
" |
| | | width="70%" |
| | | @close="closeDia" |
| | | > |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :tableLoading="tableLoading" |
| | | height="600" |
| | | ></PIMTable> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {ref} from "vue"; |
| | | import {staffOnJobInfo} from "@/api/personnelManagement/employeeRecord.js"; |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits(['close']) |
| | | |
| | | const dialogFormVisible = ref(false); |
| | | const operationType = ref('') |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "ååå¹´é", |
| | | prop: "contractTerm", |
| | | }, |
| | | { |
| | | label: "ååå¼å§æ¥æ", |
| | | prop: "contractStartTime", |
| | | }, |
| | | { |
| | | label: "ååç»ææ¥æ", |
| | | prop: "contractEndTime", |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const tableLoading = ref(false); |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = (type, row) => { |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | if (operationType.value === 'edit') { |
| | | staffOnJobInfo({staffNo: row.staffNo}).then(res => { |
| | | tableData.value = res.data |
| | | }) |
| | | } |
| | | } |
| | | |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | dialogFormVisible.value = false; |
| | | emit('close') |
| | | }; |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">å§åï¼</span> |
| | | <el-input |
| | | v-model="searchForm.staffName" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥å§åæç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | /> |
| | | <span style="margin-left: 10px" class="search_title">ååç»ææ¥æï¼</span> |
| | | <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" |
| | | placeholder="è¯·éæ©" clearable @change="changeDaterange" /> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px" |
| | | >æç´¢</el-button |
| | | > |
| | | </div> |
| | | <div style="margin: 10PX 0;"> |
| | | <!-- <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> |
| | | <div class="table_list"> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total" |
| | | ></PIMTable> |
| | | </div> |
| | | <form-dia ref="formDia" @close="handleQuery"></form-dia> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import {onMounted, ref} from "vue"; |
| | | import FormDia from "@/views/personnelManagement/employeeRecord/components/formDia.vue"; |
| | | import {ElMessageBox} from "element-plus"; |
| | | import {staffOnJobListPage} from "@/api/personnelManagement/employeeRecord.js"; |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | | staffName: "", |
| | | entryDate: [ |
| | | dayjs().format("YYYY-MM-DD"), |
| | | dayjs().add(1, "day").format("YYYY-MM-DD"), |
| | | ], // å½å
¥æ¥æ |
| | | entryDateStart: dayjs().format("YYYY-MM-DD"), |
| | | entryDateEnd: dayjs().add(1, "day").format("YYYY-MM-DD"), |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "ç¶æ", |
| | | prop: "staffState", |
| | | dataType: "tag", |
| | | formatData: (params) => { |
| | | if (params == 0) { |
| | | return "离è"; |
| | | } else if (params == 1) { |
| | | return "å¨è"; |
| | | } else { |
| | | return null; |
| | | } |
| | | }, |
| | | formatType: (params) => { |
| | | if (params == 0) { |
| | | return "danger"; |
| | | } else if (params == 1) { |
| | | return "primary"; |
| | | } else { |
| | | return null; |
| | | } |
| | | }, |
| | | }, |
| | | { |
| | | label: "åå·¥ç¼å·", |
| | | prop: "staffNo", |
| | | }, |
| | | { |
| | | label: "å§å", |
| | | prop: "staffName", |
| | | }, |
| | | { |
| | | label: "æ§å«", |
| | | prop: "sex", |
| | | }, |
| | | { |
| | | label: "ç±è´¯", |
| | | prop: "nativePlace", |
| | | }, |
| | | { |
| | | label: "å²ä½", |
| | | prop: "postJob", |
| | | }, |
| | | { |
| | | label: "å®¶åºä½å", |
| | | prop: "adress", |
| | | width:200 |
| | | }, |
| | | { |
| | | label: "第ä¸å¦å", |
| | | prop: "firstStudy", |
| | | }, |
| | | { |
| | | label: "ä¸ä¸", |
| | | prop: "profession", |
| | | width:100 |
| | | }, |
| | | { |
| | | label: "身份è¯å·", |
| | | prop: "identityCard", |
| | | width:200 |
| | | }, |
| | | { |
| | | label: "å¹´é¾", |
| | | prop: "age", |
| | | }, |
| | | { |
| | | label: "èç³»çµè¯", |
| | | prop: "phone", |
| | | width:150 |
| | | }, |
| | | { |
| | | label: "ç´§æ¥è系人", |
| | | prop: "emergencyContact", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "ç´§æ¥è系人çµè¯", |
| | | prop: "emergencyContactPhone", |
| | | width:150 |
| | | }, |
| | | { |
| | | label: "ååå¹´é", |
| | | prop: "contractTerm", |
| | | }, |
| | | // { |
| | | // label: "ååå¼å§æ¥æ", |
| | | // prop: "contractStartTime", |
| | | // width: 120 |
| | | // }, |
| | | { |
| | | label: "ååç»ææ¥æ", |
| | | prop: "contractExpireTime", |
| | | width: 120 |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: 'right', |
| | | operation: [ |
| | | { |
| | | name: "详æ
", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | total: 0 |
| | | }); |
| | | const formDia = ref() |
| | | const { proxy } = getCurrentInstance() |
| | | |
| | | const changeDaterange = (value) => { |
| | | searchForm.value.entryDateStart = undefined; |
| | | searchForm.value.entryDateEnd = undefined; |
| | | if (value) { |
| | | searchForm.value.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD"); |
| | | searchForm.value.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD"); |
| | | } |
| | | getList(); |
| | | }; |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const pagination = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | const params = { ...searchForm.value, ...page }; |
| | | params.entryDate = undefined |
| | | staffOnJobListPage({...params, staffState: 1}).then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records |
| | | page.total = res.data.total; |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | }) |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = (type, row) => { |
| | | nextTick(() => { |
| | | formDia.value?.openDialog(type, row) |
| | | }) |
| | | }; |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/staff/staffOnJob/export", {staffState: 1}, "å¨èåå·¥å°è´¦.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped></style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog |
| | | v-model="dialogFormVisible" |
| | | :title="operationType === 'add' ? 'æ°å¢å
¥è' : 'ç¼è¾äººå'" |
| | | width="70%" |
| | | @close="closeDia" |
| | | > |
| | | <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef"> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åå·¥ç¼å·ï¼" prop="staffNo"> |
| | | <el-input v-model="form.staffNo" placeholder="请è¾å
¥" clearable :disabled="operationType !== 'add'"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å§åï¼" prop="staffName"> |
| | | <el-input v-model="form.staffName" placeholder="请è¾å
¥" clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ§å«ï¼" prop="sex"> |
| | | <el-select v-model="form.sex"> |
| | | <el-option label="ç·" value="ç·" /> |
| | | <el-option label="女" value="女" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç±è´¯ï¼" prop="nativePlace"> |
| | | <el-input v-model="form.nativePlace" placeholder="请è¾å
¥" clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å²ä½ï¼" prop="postJob"> |
| | | <el-input v-model="form.postJob" placeholder="请è¾å
¥" clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å®¶åºä½åï¼" prop="adress"> |
| | | <el-input v-model="form.adress" placeholder="请è¾å
¥" clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="第ä¸å¦åï¼" prop="firstStudy"> |
| | | <el-input v-model="form.firstStudy" placeholder="请è¾å
¥" clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¸ä¸ï¼" prop="profession"> |
| | | <el-input v-model="form.profession" placeholder="请è¾å
¥" clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="身份è¯å·ï¼" prop="identityCard"> |
| | | <el-input v-model="form.identityCard" placeholder="请è¾å
¥" clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¹´é¾ï¼" prop="age"> |
| | | <el-input-number v-model="form.age" :precision="0" :step="1" style="width: 100%"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èç³»çµè¯ï¼" prop="phone"> |
| | | <el-input v-model="form.phone" placeholder="请è¾å
¥" clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç´§æ¥è系人ï¼" prop="emergencyContact"> |
| | | <el-input v-model="form.emergencyContact" placeholder="请è¾å
¥" clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç´§æ¥è系人èç³»çµè¯ï¼" prop="emergencyContactPhone"> |
| | | <el-input v-model="form.emergencyContactPhone" placeholder="请è¾å
¥" clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ååå¹´éï¼" prop="contractTerm"> |
| | | <el-input-number v-model="form.contractTerm" :precision="0" :step="1" style="width: 100%" :disabled="true"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ååå¼å§æ¥æï¼" prop="contractStartTime"> |
| | | <el-date-picker |
| | | v-model="form.contractStartTime" |
| | | type="date" |
| | | placeholder="è¯·éæ©æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | clearable |
| | | style="width: 100%" |
| | | @change="calculateContractTerm" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ååç»ææ¥æï¼" prop="contractEndTime"> |
| | | <el-date-picker |
| | | v-model="form.contractEndTime" |
| | | type="date" |
| | | placeholder="è¯·éæ©æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | clearable |
| | | style="width: 100%" |
| | | @change="calculateContractTerm" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitForm">确认</el-button> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {ref} from "vue"; |
| | | import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js"; |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits(['close']) |
| | | |
| | | const dialogFormVisible = ref(false); |
| | | const operationType = ref('') |
| | | const data = reactive({ |
| | | form: { |
| | | staffNo: "", |
| | | staffName: "", |
| | | sex: "", |
| | | nativePlace: "", |
| | | postJob: "", |
| | | adress: "", |
| | | firstStudy: "", |
| | | profession: "", |
| | | identityCard: "", |
| | | age: 0, |
| | | phone: "", |
| | | emergencyContact: "", |
| | | emergencyContactPhone: "", |
| | | contractTerm: 0, |
| | | contractStartTime: "", |
| | | contractEndTime: "", |
| | | staffState: "", |
| | | }, |
| | | rules: { |
| | | staffNo: [{ required: true, message: "请è¾å
¥", trigger: "blur" },], |
| | | staffName: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | sex: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | nativePlace: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | postJob: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | adress: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | firstStudy: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | profession: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | identityCard: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | age: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | phone: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | emergencyContact: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | emergencyContactPhone: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | contractTerm: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | contractStartTime: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | contractEndTime: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | }, |
| | | }); |
| | | const { form, rules } = toRefs(data); |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = (type, row) => { |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | if (operationType.value === 'edit') { |
| | | getStaffJoinInfo(row.id).then(res => { |
| | | form.value = {...res.data} |
| | | // ç¼è¾æ¶ä¹è®¡ç®ä¸æ¬¡ååå¹´é |
| | | calculateContractTerm(); |
| | | }) |
| | | } |
| | | } |
| | | // æäº¤äº§å表å |
| | | const submitForm = () => { |
| | | proxy.$refs.formRef.validate(valid => { |
| | | if (valid) { |
| | | form.value.staffState = 1 |
| | | if (operationType.value === "add") { |
| | | staffJoinAdd(form.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | }) |
| | | } else { |
| | | staffJoinUpdate(form.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | }) |
| | | } |
| | | } |
| | | }) |
| | | } |
| | | // 计ç®ååå¹´é |
| | | const calculateContractTerm = () => { |
| | | if (form.value.contractStartTime && form.value.contractEndTime) { |
| | | const startDate = new Date(form.value.contractStartTime); |
| | | const endDate = new Date(form.value.contractEndTime); |
| | | |
| | | if (endDate > startDate) { |
| | | // 计ç®å¹´ä»½å·® |
| | | const yearDiff = endDate.getFullYear() - startDate.getFullYear(); |
| | | const monthDiff = endDate.getMonth() - startDate.getMonth(); |
| | | const dayDiff = endDate.getDate() - startDate.getDate(); |
| | | |
| | | let years = yearDiff; |
| | | |
| | | // å¦æç»ææ¥æçææ¥å°äºå¼å§æ¥æçææ¥ï¼ååå»1å¹´ |
| | | if (monthDiff < 0 || (monthDiff === 0 && dayDiff < 0)) { |
| | | years = yearDiff - 1; |
| | | } |
| | | |
| | | form.value.contractTerm = Math.max(0, years); |
| | | } else { |
| | | form.value.contractTerm = 0; |
| | | } |
| | | } else { |
| | | form.value.contractTerm = 0; |
| | | } |
| | | }; |
| | | |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | | dialogFormVisible.value = false; |
| | | emit('close') |
| | | }; |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">å§åï¼</span> |
| | | <el-input |
| | | v-model="searchForm.staffName" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥å§åæç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | /> |
| | | <span style="margin-left: 10px;" class="search_title">ååå¼å§æ¥æï¼</span> |
| | | <el-date-picker |
| | | v-model="searchForm.entryDateStart" |
| | | type="date" |
| | | placeholder="è¯·éæ©ååå¼å§æ¥æ" |
| | | size="default" |
| | | @change="(date) => handleDateChange(date,1)" |
| | | /> |
| | | <span style="margin-left: 10px;" class="search_title">ååç»ææ¥æï¼</span> |
| | | <el-date-picker |
| | | v-model="searchForm.entryDateEnd" |
| | | type="date" |
| | | placeholder="è¯·éæ©ååç»ææ¥æ" |
| | | size="default" |
| | | @change="(date) => handleDateChange(date,2)" |
| | | /> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px">æç´¢</el-button> |
| | | </div> |
| | | <div style="margin: 10PX 0;"> |
| | | <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> |
| | | <div class="table_list"> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total" |
| | | ></PIMTable> |
| | | </div> |
| | | <form-dia ref="formDia" @close="handleQuery"></form-dia> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import {onMounted, ref} from "vue"; |
| | | import FormDia from "@/views/personnelManagement/onboarding/components/formDia.vue"; |
| | | import {staffJoinDel, staffJoinListPage} from "@/api/personnelManagement/onboarding.js"; |
| | | import {ElMessageBox} from "element-plus"; |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | | staffName: "", |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "ç¶æ", |
| | | prop: "staffState", |
| | | dataType: "tag", |
| | | formatData: (params) => { |
| | | if (params == 0) { |
| | | return "离è"; |
| | | } else if (params == 1) { |
| | | return "å
¥è"; |
| | | } else { |
| | | return null; |
| | | } |
| | | }, |
| | | formatType: (params) => { |
| | | if (params == 0) { |
| | | return "danger"; |
| | | } else if (params == 1) { |
| | | return "primary"; |
| | | } else { |
| | | return null; |
| | | } |
| | | }, |
| | | }, |
| | | { |
| | | label: "åå·¥ç¼å·", |
| | | prop: "staffNo", |
| | | }, |
| | | { |
| | | label: "å§å", |
| | | prop: "staffName", |
| | | }, |
| | | { |
| | | label: "æ§å«", |
| | | prop: "sex", |
| | | }, |
| | | { |
| | | label: "ç±è´¯", |
| | | prop: "nativePlace", |
| | | }, |
| | | { |
| | | label: "å²ä½", |
| | | prop: "postJob", |
| | | }, |
| | | { |
| | | label: "å®¶åºä½å", |
| | | prop: "adress", |
| | | width:200 |
| | | }, |
| | | { |
| | | label: "第ä¸å¦å", |
| | | prop: "firstStudy", |
| | | }, |
| | | { |
| | | label: "ä¸ä¸", |
| | | prop: "profession", |
| | | width:100 |
| | | }, |
| | | { |
| | | label: "身份è¯å·", |
| | | prop: "identityCard", |
| | | width:200 |
| | | }, |
| | | { |
| | | label: "å¹´é¾", |
| | | prop: "age", |
| | | }, |
| | | { |
| | | label: "èç³»çµè¯", |
| | | prop: "phone", |
| | | width:150 |
| | | }, |
| | | { |
| | | label: "ç´§æ¥è系人", |
| | | prop: "emergencyContact", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "èç³»çµè¯", |
| | | prop: "emergencyContactPhone", |
| | | width:150 |
| | | }, |
| | | { |
| | | label: "ååå¹´é", |
| | | prop: "contractTerm", |
| | | }, |
| | | { |
| | | label: "ååå¼å§æ¥æ", |
| | | prop: "contractStartTime", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "ååç»ææ¥æ", |
| | | prop: "contractEndTime", |
| | | width: 120 |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: 'right', |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | total: 0, |
| | | }); |
| | | const formDia = ref() |
| | | const { proxy } = getCurrentInstance() |
| | | |
| | | const handleDateChange = (value,type) => { |
| | | searchForm.value.entryDateEnd = null |
| | | searchForm.value.entryDateStart = null |
| | | if(type === 1){ |
| | | if (value) { |
| | | searchForm.value.entryDateStart = dayjs(value).format("YYYY-MM-DD"); |
| | | } |
| | | }else{ |
| | | if (value) { |
| | | searchForm.value.entryDateEnd = dayjs(value).format("YYYY-MM-DD"); |
| | | } |
| | | } |
| | | getList(); |
| | | }; |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const pagination = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | staffJoinListPage({...page, ...searchForm.value, staffState: 1}).then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records |
| | | page.total = res.data.total; |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | }) |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = (type, row) => { |
| | | nextTick(() => { |
| | | formDia.value?.openDialog(type, row) |
| | | }) |
| | | }; |
| | | |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | ids = selectedRows.value.map((item) => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | staffJoinDel(ids).then((res) => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/staff/staffJoinLeaveRecord/export", {staffState: 1}, "人åå
¥è.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped></style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog |
| | | v-model="dialogFormVisible" |
| | | :title="operationType === 'add' ? 'æ°å¢å
¥è' : 'ç¼è¾äººå'" |
| | | width="50%" |
| | | @close="closeDia" |
| | | > |
| | | <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef"> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æä»½ï¼" prop="payDate"> |
| | | <el-date-picker |
| | | v-model="form.payDate" |
| | | type="month" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM" |
| | | placeholder="è¯·éæ©æä»½" |
| | | clearable |
| | | :disabled="operationType === 'edit'" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å§åï¼" prop="staffId"> |
| | | <el-select v-model="form.staffId" placeholder="è¯·éæ©äººå" style="width: 100%" @change="handleSelect" :disabled="operationType === 'edit'"> |
| | | <el-option |
| | | v-for="item in personList" |
| | | :key="item.id" |
| | | :label="item.staffName" |
| | | :value="item.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åºåºå¤å¤©æ°ï¼" prop="shouldAttendedNum"> |
| | | <el-input v-model="form.shouldAttendedNum" placeholder="请è¾å
¥" clearable type="number"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å®é
åºå¤å¤©æ°ï¼" prop="actualAttendedNum"> |
| | | <el-input v-model="form.actualAttendedNum" placeholder="请è¾å
¥" clearable type="number"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åºæ¬å·¥èµï¼" prop="basicSalary"> |
| | | <el-input v-model="form.basicSalary" placeholder="请è¾å
¥" clearable type="number"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å²ä½å·¥èµï¼" prop="postSalary"> |
| | | <el-input v-model="form.postSalary" placeholder="请è¾å
¥" clearable type="number"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å
¥ç¦»èç¼ºå¤æ£æ¬¾ï¼" prop="deductionAbsenteeism"> |
| | | <el-input v-model="form.deductionAbsenteeism" placeholder="请è¾å
¥" clearable type="number"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç
åæ£æ¬¾ï¼" prop="sickLeaveDeductions"> |
| | | <el-input v-model="form.sickLeaveDeductions" placeholder="请è¾å
¥" clearable type="number"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="äºåæ£æ¬¾ï¼" prop="deductionPersonalLeave"> |
| | | <el-input v-model="form.deductionPersonalLeave" placeholder="请è¾å
¥" clearable type="number"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¿è®°æå¡æ£æ¬¾ï¼" prop="forgetClockDeduct"> |
| | | <el-input v-model="form.forgetClockDeduct" style="width: 100%" type="number"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="绩æå¾åï¼" prop="performanceScore"> |
| | | <el-input v-model="form.performanceScore" placeholder="请è¾å
¥" clearable type="number"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="绩æå·¥èµï¼" prop="performancePay"> |
| | | <el-input v-model="form.performancePay" placeholder="请è¾å
¥" clearable type="number"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åºåå计ï¼" prop="payableWages"> |
| | | <el-input v-model="form.payableWages" placeholder="请è¾å
¥" clearable type="number"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="社ä¿ä¸ªäººï¼" prop="socialSecurityIndividuals"> |
| | | <el-input v-model="form.socialSecurityIndividuals" :precision="0" :step="1" style="width: 100%" type="number"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="社ä¿å
¬å¸ï¼" prop="socialSecurityCompanies"> |
| | | <el-input v-model="form.socialSecurityCompanies" :precision="0" :step="1" style="width: 100%" type="number"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="社ä¿å计ï¼" prop="socialSecurityTotal"> |
| | | <el-input v-model="form.socialSecurityTotal" :precision="0" :step="1" style="width: 100%" type="number"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å
¬ç§¯é个人ï¼" prop="providentFundIndividuals"> |
| | | <el-input v-model="form.providentFundIndividuals" :precision="0" :step="1" style="width: 100%" type="number"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å
¬ç§¯éå
¬å¸ï¼" prop="providentFundCompany"> |
| | | <el-input v-model="form.providentFundCompany" :precision="0" :step="1" style="width: 100%" type="number"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å
¬ç§¯éå计ï¼" prop="providentFundTotal"> |
| | | <el-input v-model="form.providentFundTotal" :precision="0" :step="1" style="width: 100%" type="number"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åºç¨å·¥èµï¼" prop="taxableWaget"> |
| | | <el-input v-model="form.taxableWaget" :precision="0" :step="1" style="width: 100%" type="number"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="个人æå¾ç¨ï¼" prop="personalIncomeTax"> |
| | | <el-input v-model="form.personalIncomeTax" :step="0.1" style="width: 100%" type="number"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å®åå·¥èµï¼" prop="actualWages"> |
| | | <el-input v-model="form.actualWages" style="width: 100%" type="number"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitForm">确认</el-button> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {ref} from "vue"; |
| | | import {getStaffJoinInfo, getStaffOnJob, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js"; |
| | | import {compensationAdd, compensationUpdate} from "@/api/personnelManagement/payrollManagement.js"; |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits(['close']) |
| | | |
| | | const dialogFormVisible = ref(false); |
| | | const operationType = ref('') |
| | | const data = reactive({ |
| | | form: { |
| | | payDate: "", |
| | | staffId: "", |
| | | name: "", |
| | | shouldAttendedNum: "", |
| | | actualAttendedNum: "", |
| | | basicSalary: "", |
| | | postSalary: "", |
| | | deductionAbsenteeism: "", |
| | | sickLeaveDeductions: "", |
| | | deductionPersonalLeave: "", |
| | | forgetClockDeduct: "", |
| | | performanceScore: "", |
| | | performancePay: "", |
| | | payableWages: "", |
| | | socialSecurityIndividuals: "", |
| | | socialSecurityCompanies: "", |
| | | socialSecurityTotal: "", |
| | | providentFundIndividuals: "", |
| | | providentFundCompany: "", |
| | | providentFundTotal: "", |
| | | taxableWaget: "", |
| | | personalIncomeTax: "", |
| | | actualWages: "", |
| | | }, |
| | | rules: { |
| | | payDate: [{ required: true, message: "è¯·éæ©", trigger: "change" },], |
| | | staffId: [{ required: true, message: "è¯·éæ©", trigger: "change" },], |
| | | staffName: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | shouldAttendedNum: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | actualAttendedNum: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | basicSalary: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | postSalary: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | deductionAbsenteeism: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | sickLeaveDeductions: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | deductionPersonalLeave: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | forgetClockDeduct: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | performanceScore: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | performancePay: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | payableWages: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | socialSecurityIndividuals: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | socialSecurityCompanies: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | socialSecurityTotal: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | providentFundIndividuals: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | providentFundCompany: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | providentFundTotal: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | taxableWaget: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | personalIncomeTax: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | actualWages: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | }, |
| | | }); |
| | | const { form, rules } = toRefs(data); |
| | | const personList = ref([]); |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = (type, row) => { |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | getStaffOnJob().then(res => { |
| | | personList.value = res.data |
| | | }) |
| | | form.value = {} |
| | | if (operationType.value === 'edit') { |
| | | getStaffJoinInfo(row.id).then(res => { |
| | | form.value = {...row} |
| | | form.value.payDate = form.value.payDate + '-01' |
| | | }) |
| | | } |
| | | } |
| | | const handleSelect = (value) => { |
| | | console.log('value', value) |
| | | const index = personList.value.findIndex(row => row.id === value) |
| | | if (index > -1) { |
| | | form.value.name = personList.value[index].staffName |
| | | } |
| | | } |
| | | // æäº¤äº§å表å |
| | | const submitForm = () => { |
| | | proxy.$refs.formRef.validate(valid => { |
| | | if (valid) { |
| | | form.value.staffState = 1 |
| | | if (operationType.value === "add") { |
| | | compensationAdd(form.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | }) |
| | | } else { |
| | | compensationUpdate(form.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | }) |
| | | } |
| | | } |
| | | }) |
| | | } |
| | | // 计ç®ååå¹´é |
| | | const calculateContractTerm = () => { |
| | | if (form.value.contractStartTime && form.value.contractEndTime) { |
| | | const startDate = new Date(form.value.contractStartTime); |
| | | const endDate = new Date(form.value.contractEndTime); |
| | | |
| | | if (endDate > startDate) { |
| | | // 计ç®å¹´ä»½å·® |
| | | const yearDiff = endDate.getFullYear() - startDate.getFullYear(); |
| | | const monthDiff = endDate.getMonth() - startDate.getMonth(); |
| | | const dayDiff = endDate.getDate() - startDate.getDate(); |
| | | |
| | | let years = yearDiff; |
| | | |
| | | // å¦æç»ææ¥æçææ¥å°äºå¼å§æ¥æçææ¥ï¼ååå»1å¹´ |
| | | if (monthDiff < 0 || (monthDiff === 0 && dayDiff < 0)) { |
| | | years = yearDiff - 1; |
| | | } |
| | | |
| | | form.value.contractTerm = Math.max(0, years); |
| | | } else { |
| | | form.value.contractTerm = 0; |
| | | } |
| | | } else { |
| | | form.value.contractTerm = 0; |
| | | } |
| | | }; |
| | | |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | | dialogFormVisible.value = false; |
| | | emit('close') |
| | | }; |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">å§åï¼</span> |
| | | <el-input |
| | | v-model="searchForm.name" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥å§åæç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | /> |
| | | <span class="search_title ml10">æä»½ï¼</span> |
| | | <el-date-picker |
| | | v-model="searchForm.payDateStr" |
| | | type="month" |
| | | @change="handleQuery" |
| | | value-format="YYYY-MM" |
| | | format="YYYY-MM" |
| | | placeholder="è¯·éæ©æä»½" |
| | | style="width: 240px" |
| | | clearable |
| | | /> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px" |
| | | >æç´¢</el-button |
| | | > |
| | | </div> |
| | | <div style="margin: 10PX 0;"> |
| | | <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> |
| | | <div class="table_list"> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total" |
| | | ></PIMTable> |
| | | </div> |
| | | <form-dia ref="formDia" @close="handleQuery"></form-dia> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import {onMounted, ref} from "vue"; |
| | | import FormDia from "@/views/personnelManagement/payrollManagement/components/formDia.vue"; |
| | | import {staffJoinDel} from "@/api/personnelManagement/onboarding.js"; |
| | | import {ElMessageBox} from "element-plus"; |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | import dayjs from "dayjs"; |
| | | import {compensationDelete, compensationListPage} from "@/api/personnelManagement/payrollManagement.js"; |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | | name: "", |
| | | payDateStr: "", |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "èªèµæä»½", |
| | | prop: "payDate", |
| | | }, |
| | | { |
| | | label: "å§å", |
| | | prop: "name", |
| | | }, |
| | | { |
| | | label: "åºåºå¤å¤©æ°", |
| | | prop: "shouldAttendedNum", |
| | | width:100 |
| | | }, |
| | | { |
| | | label: "å®é
åºå¤å¤©æ°", |
| | | prop: "actualAttendedNum", |
| | | width:110 |
| | | }, |
| | | { |
| | | label: "åºæ¬å·¥èµ", |
| | | prop: "basicSalary", |
| | | }, |
| | | { |
| | | label: "å²ä½å·¥èµ", |
| | | prop: "postSalary", |
| | | width:100 |
| | | }, |
| | | { |
| | | label: "å
¥ç¦»èç¼ºå¤æ£æ¬¾", |
| | | prop: "deductionAbsenteeism", |
| | | width:130 |
| | | }, |
| | | { |
| | | label: "ç
åæ£æ¬¾", |
| | | prop: "sickLeaveDeductions", |
| | | width:100 |
| | | }, |
| | | { |
| | | label: "äºåæ£æ¬¾", |
| | | prop: "deductionPersonalLeave", |
| | | width:100 |
| | | }, |
| | | { |
| | | label: "å¿è®°æå¡æ£æ¬¾", |
| | | prop: "forgetClockDeduct", |
| | | width:110 |
| | | }, |
| | | { |
| | | label: "绩æå¾å", |
| | | prop: "performanceScore", |
| | | width:150 |
| | | }, |
| | | { |
| | | label: "绩æå·¥èµ", |
| | | prop: "performancePay", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "åºåå计", |
| | | prop: "payableWages", |
| | | width:150 |
| | | }, |
| | | { |
| | | label: "社ä¿ä¸ªäºº", |
| | | prop: "socialSecurityIndividuals", |
| | | }, |
| | | { |
| | | label: "社ä¿å
¬å¸", |
| | | prop: "socialSecurityCompanies", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "社ä¿å计", |
| | | prop: "socialSecurityTotal", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "å
¬ç§¯é个人", |
| | | prop: "providentFundIndividuals", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "å
¬ç§¯éå
¬å¸", |
| | | prop: "providentFundCompany", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "å
¬ç§¯éå计", |
| | | prop: "providentFundTotal", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "åºç¨å·¥èµ", |
| | | prop: "taxableWaget", |
| | | }, |
| | | { |
| | | label: "个人æå¾ç¨", |
| | | prop: "personalIncomeTax", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "å®åå·¥èµ", |
| | | prop: "actualWages", |
| | | width: 120 |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: 'right', |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | total: 0, |
| | | }); |
| | | const formDia = ref() |
| | | const { proxy } = getCurrentInstance() |
| | | |
| | | const handleDateChange = (value,type) => { |
| | | searchForm.value.entryDateEnd = null |
| | | searchForm.value.entryDateStart = null |
| | | if(type === 1){ |
| | | if (value) { |
| | | searchForm.value.entryDateStart = dayjs(value).format("YYYY-MM-DD"); |
| | | } |
| | | }else{ |
| | | if (value) { |
| | | searchForm.value.entryDateEnd = dayjs(value).format("YYYY-MM-DD"); |
| | | } |
| | | } |
| | | getList(); |
| | | }; |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const pagination = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | compensationListPage({...page, ...searchForm.value, staffState: 1}).then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records |
| | | page.total = res.data.total; |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | }) |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = (type, row) => { |
| | | nextTick(() => { |
| | | formDia.value?.openDialog(type, row) |
| | | }) |
| | | }; |
| | | |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | ids = selectedRows.value.map((item) => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | compensationDelete(ids).then((res) => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/staff/staffJoinLeaveRecord/export", {staffState: 1}, "人åå
¥è.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped></style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container scheduling-container"> |
| | | <!-- çéåºå --> |
| | | <div class="filter-section"> |
| | | <el-form :inline="true" :model="filterForm" class="filter-form"> |
| | | <el-form-item label="åå·¥å§åï¼"> |
| | | <el-input |
| | | v-model="filterForm.employeeName" |
| | | placeholder="请è¾å
¥åå·¥å§å" |
| | | clearable |
| | | style="width: 150px" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="çæ¬¡ç±»åï¼"> |
| | | <el-select v-model="filterForm.shiftType" placeholder="è¯·éæ©çæ¬¡" clearable style="width: 120px"> |
| | | <el-option label="æ©ç" value="æ©ç" /> |
| | | <el-option label="ä¸ç" value="ä¸ç" /> |
| | | <el-option label="æç" value="æç" /> |
| | | <el-option label="å¤ç" value="å¤ç" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="æ¥æèå´ï¼"> |
| | | <el-date-picker |
| | | v-model="filterForm.dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 250px" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleFilter"> |
| | | <el-icon><Search /></el-icon> |
| | | çé |
| | | </el-button> |
| | | <el-button @click="resetFilter"> |
| | | <el-icon><Refresh /></el-icon> |
| | | éç½® |
| | | </el-button> |
| | | <el-button type="primary" @click="openScheduleDialog('add')"> |
| | | <el-icon><Plus /></el-icon> |
| | | æ°å¢æç |
| | | </el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | |
| | | <!-- æçè¡¨æ ¼ --> |
| | | <div class="table-section"> |
| | | <el-table |
| | | :data="filteredScheduleList" |
| | | border |
| | | stripe |
| | | style="width: 100%" |
| | | height="calc(100vh - 18.5em)" |
| | | @selection-change="handleSelectionChange" |
| | | > |
| | | <el-table-column type="selection" width="55" /> |
| | | <el-table-column prop="employeeName" label="åå·¥å§å" width="120" /> |
| | | <el-table-column prop="employeeId" label="å工工å·" width="100" /> |
| | | <el-table-column prop="department" label="é¨é¨" width="120" /> |
| | | <el-table-column prop="shiftType" label="çæ¬¡ç±»å" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getShiftTagType(scope.row.shiftType)"> |
| | | {{ scope.row.shiftType }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="workDate" label="工使¥æ" width="120" /> |
| | | <el-table-column prop="startTime" label="å¼å§æ¶é´" width="100" /> |
| | | <el-table-column prop="endTime" label="ç»ææ¶é´" width="100" /> |
| | | <el-table-column prop="workHours" label="工使¶é¿" width="100"> |
| | | <template #default="scope"> |
| | | {{ scope.row.workHours }}å°æ¶ |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="status" label="ç¶æ" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getStatusTagType(scope.row.status)"> |
| | | {{ scope.row.status }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="remark" label="夿³¨" min-width="150" /> |
| | | <el-table-column label="æä½" width="200" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button |
| | | type="primary" |
| | | size="small" |
| | | @click="openScheduleDialog('edit', scope.row)" |
| | | > |
| | | ç¼è¾ |
| | | </el-button> |
| | | <el-button |
| | | type="danger" |
| | | size="small" |
| | | @click="handleDelete(scope.row)" |
| | | > |
| | | å é¤ |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <!-- æ¹éæä½ --> |
| | | <div class="batch-actions" v-if="selectedRows.length > 0"> |
| | | <el-button |
| | | type="danger" |
| | | @click="handleBatchDelete" |
| | | :disabled="selectedRows.length === 0" |
| | | > |
| | | æ¹éå é¤ ({{ selectedRows.length }}) |
| | | </el-button> |
| | | </div> |
| | | |
| | | <!-- æçæ°å¢/ç¼è¾å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | v-model="scheduleDialog" |
| | | :title="dialogType === 'add' ? 'æ°å¢æç' : 'ç¼è¾æç'" |
| | | width="700px" |
| | | @close="closeScheduleDialog" |
| | | > |
| | | <el-form |
| | | :model="scheduleForm" |
| | | :rules="scheduleRules" |
| | | ref="scheduleFormRef" |
| | | label-width="120px" |
| | | > |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åå·¥å§åï¼" prop="employeeName"> |
| | | <el-input v-model="scheduleForm.employeeName" placeholder="请è¾å
¥åå·¥å§å" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å工工å·ï¼" prop="employeeId"> |
| | | <el-input v-model="scheduleForm.employeeId" placeholder="请è¾å
¥å工工å·" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="é¨é¨ï¼" prop="department"> |
| | | <el-select v-model="scheduleForm.department" placeholder="è¯·éæ©é¨é¨" style="width: 100%"> |
| | | <el-option label="ææ¯é¨" value="ææ¯é¨" /> |
| | | <el-option label="éå®é¨" value="éå®é¨" /> |
| | | <el-option label="人äºé¨" value="人äºé¨" /> |
| | | <el-option label="è´¢å¡é¨" value="è´¢å¡é¨" /> |
| | | <el-option label="ç产é¨" value="ç产é¨" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="çæ¬¡ç±»åï¼" prop="shiftType"> |
| | | <el-select v-model="scheduleForm.shiftType" placeholder="è¯·éæ©çæ¬¡" style="width: 100%"> |
| | | <el-option label="æ©ç" value="æ©ç" /> |
| | | <el-option label="ä¸ç" value="ä¸ç" /> |
| | | <el-option label="æç" value="æç" /> |
| | | <el-option label="å¤ç" value="å¤ç" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="工使¥æï¼" prop="workDate"> |
| | | <el-date-picker |
| | | v-model="scheduleForm.workDate" |
| | | type="date" |
| | | placeholder="鿩工使¥æ" |
| | | style="width: 100%" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¶æï¼" prop="status"> |
| | | <el-select v-model="scheduleForm.status" placeholder="è¯·éæ©ç¶æ" style="width: 100%"> |
| | | <el-option label="已宿" value="已宿" /> |
| | | <el-option label="已确认" value="已确认" /> |
| | | <el-option label="已宿" value="已宿" /> |
| | | <el-option label="已忶" value="已忶" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¼å§æ¶é´ï¼" prop="startTime"> |
| | | <el-time-picker |
| | | v-model="scheduleForm.startTime" |
| | | placeholder="éæ©å¼å§æ¶é´" |
| | | style="width: 100%" |
| | | format="HH:mm" |
| | | value-format="HH:mm" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç»ææ¶é´ï¼" prop="endTime"> |
| | | <el-time-picker |
| | | v-model="scheduleForm.endTime" |
| | | placeholder="éæ©ç»ææ¶é´" |
| | | style="width: 100%" |
| | | format="HH:mm" |
| | | value-format="HH:mm" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="夿³¨ï¼" prop="remark"> |
| | | <el-input |
| | | v-model="scheduleForm.remark" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitScheduleForm">确认</el-button> |
| | | <el-button @click="closeScheduleDialog">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed, onMounted } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { Plus, Download, Search, Refresh } from '@element-plus/icons-vue' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const scheduleDialog = ref(false) |
| | | const dialogType = ref('add') |
| | | const selectedRows = ref([]) |
| | | const scheduleFormRef = ref() |
| | | |
| | | // çé表å |
| | | const filterForm = reactive({ |
| | | employeeName: '', |
| | | shiftType: '', |
| | | dateRange: [] |
| | | }) |
| | | |
| | | // æç表å |
| | | const scheduleForm = reactive({ |
| | | id: '', |
| | | employeeName: '', |
| | | employeeId: '', |
| | | department: '', |
| | | shiftType: '', |
| | | workDate: '', |
| | | startTime: '', |
| | | endTime: '', |
| | | workHours: 0, |
| | | status: '已宿', |
| | | remark: '' |
| | | }) |
| | | |
| | | // 表åéªè¯è§å |
| | | const scheduleRules = reactive({ |
| | | employeeName: [{ required: true, message: '请è¾å
¥åå·¥å§å', trigger: 'blur' }], |
| | | employeeId: [{ required: true, message: '请è¾å
¥å工工å·', trigger: 'blur' }], |
| | | department: [{ required: true, message: 'è¯·éæ©é¨é¨', trigger: 'change' }], |
| | | shiftType: [{ required: true, message: 'è¯·éæ©çæ¬¡ç±»å', trigger: 'change' }], |
| | | workDate: [{ required: true, message: 'è¯·éæ©å·¥ä½æ¥æ', trigger: 'change' }], |
| | | startTime: [{ required: true, message: 'è¯·éæ©å¼å§æ¶é´', trigger: 'change' }], |
| | | endTime: [{ required: true, message: 'è¯·éæ©ç»ææ¶é´', trigger: 'change' }], |
| | | status: [{ required: true, message: 'è¯·éæ©ç¶æ', trigger: 'change' }] |
| | | }) |
| | | |
| | | // 模ææçæ°æ® |
| | | const scheduleList = ref([ |
| | | { |
| | | id: 1, |
| | | employeeName: 'å¼ æµ·æ´', |
| | | employeeId: 'EMP001', |
| | | department: 'ææ¯é¨', |
| | | shiftType: 'æ©ç', |
| | | workDate: '2024-01-15', |
| | | startTime: '08:00', |
| | | endTime: '17:00', |
| | | workHours: 9, |
| | | status: '已宿', |
| | | remark: 'æ£å¸¸æç' |
| | | }, |
| | | { |
| | | id: 2, |
| | | employeeName: 'æè¶
', |
| | | employeeId: 'EMP002', |
| | | department: 'éå®é¨', |
| | | shiftType: 'ä¸ç', |
| | | workDate: '2024-01-15', |
| | | startTime: '14:00', |
| | | endTime: '22:00', |
| | | workHours: 8, |
| | | status: '已确认', |
| | | remark: '客æ·ä¼è®®' |
| | | }, |
| | | { |
| | | id: 3, |
| | | employeeName: 'çæ°', |
| | | employeeId: 'EMP003', |
| | | department: 'ç产é¨', |
| | | shiftType: 'æç', |
| | | workDate: '2024-01-15', |
| | | startTime: '22:00', |
| | | endTime: '06:00', |
| | | workHours: 8, |
| | | status: '已宿', |
| | | remark: 'å¤çç产' |
| | | } |
| | | ]) |
| | | |
| | | // 计ç®å±æ§ï¼çéåçæçå表 |
| | | const filteredScheduleList = computed(() => { |
| | | let result = scheduleList.value |
| | | |
| | | if (filterForm.employeeName) { |
| | | result = result.filter(item => |
| | | item.employeeName.includes(filterForm.employeeName) |
| | | ) |
| | | } |
| | | |
| | | if (filterForm.shiftType) { |
| | | result = result.filter(item => item.shiftType === filterForm.shiftType) |
| | | } |
| | | |
| | | if (filterForm.dateRange && filterForm.dateRange.length === 2) { |
| | | result = result.filter(item => { |
| | | const workDate = new Date(item.workDate) |
| | | const startDate = new Date(filterForm.dateRange[0]) |
| | | const endDate = new Date(filterForm.dateRange[1]) |
| | | return workDate >= startDate && workDate <= endDate |
| | | }) |
| | | } |
| | | |
| | | return result |
| | | }) |
| | | |
| | | // è·åçæ¬¡æ ç¾ç±»å |
| | | const getShiftTagType = (shiftType) => { |
| | | const typeMap = { |
| | | 'æ©ç': 'success', |
| | | 'ä¸ç': 'warning', |
| | | 'æç': 'info', |
| | | 'å¤ç': 'danger' |
| | | } |
| | | return typeMap[shiftType] || 'info' |
| | | } |
| | | |
| | | // è·åç¶ææ ç¾ç±»å |
| | | const getStatusTagType = (status) => { |
| | | const typeMap = { |
| | | '已宿': 'info', |
| | | '已确认': 'warning', |
| | | '已宿': 'success', |
| | | '已忶': 'danger' |
| | | } |
| | | return typeMap[status] || 'info' |
| | | } |
| | | |
| | | // çé |
| | | const handleFilter = () => { |
| | | // çéé»è¾å·²å¨è®¡ç®å±æ§ä¸å®ç° |
| | | } |
| | | |
| | | // éç½®çé |
| | | const resetFilter = () => { |
| | | filterForm.employeeName = '' |
| | | filterForm.shiftType = '' |
| | | filterForm.dateRange = [] |
| | | } |
| | | |
| | | // æå¼æçå¯¹è¯æ¡ |
| | | const openScheduleDialog = (type, data) => { |
| | | dialogType.value = type |
| | | scheduleDialog.value = true |
| | | |
| | | if (type === 'edit' && data) { |
| | | // ç¼è¾æ¨¡å¼ï¼å¤å¶æ°æ® |
| | | Object.assign(scheduleForm, { ...data }) |
| | | } else { |
| | | // æ°å¢æ¨¡å¼ï¼é置表å |
| | | Object.keys(scheduleForm).forEach(key => { |
| | | scheduleForm[key] = '' |
| | | }) |
| | | scheduleForm.status = '已宿' |
| | | scheduleForm.workDate = new Date().toISOString().split('T')[0] |
| | | } |
| | | } |
| | | |
| | | // å
³éæçå¯¹è¯æ¡ |
| | | const closeScheduleDialog = () => { |
| | | scheduleFormRef.value?.resetFields() |
| | | scheduleDialog.value = false |
| | | } |
| | | |
| | | // 计ç®å·¥ä½æ¶é¿ |
| | | const calculateWorkHours = () => { |
| | | if (scheduleForm.startTime && scheduleForm.endTime) { |
| | | const start = new Date(`2000-01-01 ${scheduleForm.startTime}`) |
| | | const end = new Date(`2000-01-01 ${scheduleForm.endTime}`) |
| | | |
| | | if (end < start) { |
| | | // 跨天çæ
åµ |
| | | end.setDate(end.getDate() + 1) |
| | | } |
| | | |
| | | const diffMs = end - start |
| | | const diffHours = diffMs / (1000 * 60 * 60) |
| | | scheduleForm.workHours = Math.round(diffHours * 100) / 100 |
| | | } |
| | | } |
| | | |
| | | // æäº¤æç表å |
| | | const submitScheduleForm = () => { |
| | | scheduleFormRef.value.validate((valid) => { |
| | | if (valid) { |
| | | // 计ç®å·¥ä½æ¶é¿ |
| | | calculateWorkHours() |
| | | |
| | | if (dialogType.value === 'add') { |
| | | // æ°å¢ |
| | | const newSchedule = { |
| | | ...scheduleForm, |
| | | id: Date.now() // ä½¿ç¨æ¶é´æ³ä½ä¸ºä¸´æ¶ID |
| | | } |
| | | scheduleList.value.push(newSchedule) |
| | | ElMessage.success('æ°å¢æçæå') |
| | | } else { |
| | | // ç¼è¾ |
| | | const index = scheduleList.value.findIndex(item => item.id === scheduleForm.id) |
| | | if (index !== -1) { |
| | | scheduleList.value[index] = { ...scheduleForm } |
| | | ElMessage.success('ç¼è¾æçæå') |
| | | } |
| | | } |
| | | |
| | | closeScheduleDialog() |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // å 餿ç |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm( |
| | | `ç¡®å®è¦å é¤ ${row.employeeName} çæçè®°å½åï¼`, |
| | | 'å é¤æç¤º', |
| | | { |
| | | confirmButtonText: '确认', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | } |
| | | ).then(() => { |
| | | const index = scheduleList.value.findIndex(item => item.id === row.id) |
| | | if (index !== -1) { |
| | | scheduleList.value.splice(index, 1) |
| | | ElMessage.success('å 餿å') |
| | | } |
| | | }).catch(() => { |
| | | ElMessage.info('已忶å é¤') |
| | | }) |
| | | } |
| | | |
| | | // æ¹éå é¤ |
| | | const handleBatchDelete = () => { |
| | | if (selectedRows.value.length === 0) { |
| | | ElMessage.warning('è¯·éæ©è¦å é¤çè®°å½') |
| | | return |
| | | } |
| | | |
| | | ElMessageBox.confirm( |
| | | `ç¡®å®è¦å é¤éä¸ç ${selectedRows.value.length} æ¡æçè®°å½åï¼`, |
| | | 'æ¹éå é¤æç¤º', |
| | | { |
| | | confirmButtonText: '确认', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | } |
| | | ).then(() => { |
| | | const selectedIds = selectedRows.value.map(row => row.id) |
| | | scheduleList.value = scheduleList.value.filter(item => !selectedIds.includes(item.id)) |
| | | selectedRows.value = [] |
| | | ElMessage.success('æ¹éå 餿å') |
| | | }).catch(() => { |
| | | ElMessage.info('已忶å é¤') |
| | | }) |
| | | } |
| | | |
| | | // éæ©ååäºä»¶ |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection |
| | | } |
| | | |
| | | // ç嬿¶é´ååï¼èªå¨è®¡ç®å·¥ä½æ¶é¿ |
| | | const watchTimeChange = () => { |
| | | if (scheduleForm.startTime && scheduleForm.endTime) { |
| | | calculateWorkHours() |
| | | } |
| | | } |
| | | |
| | | // çå½å¨æ |
| | | onMounted(() => { |
| | | // 页é¢åå§å |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .scheduling-container { |
| | | padding: 20px; |
| | | background-color: #f5f7fa; |
| | | min-height: 100vh; |
| | | } |
| | | |
| | | .page-header { |
| | | text-align: center; |
| | | margin-bottom: 30px; |
| | | padding: 20px; |
| | | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | | border-radius: 12px; |
| | | color: white; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | color: white; |
| | | margin-bottom: 10px; |
| | | font-size: 28px; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .page-header p { |
| | | color: rgba(255, 255, 255, 0.9); |
| | | font-size: 14px; |
| | | margin: 0 0 15px 0; |
| | | } |
| | | |
| | | .header-controls { |
| | | display: flex; |
| | | justify-content: center; |
| | | gap: 15px; |
| | | } |
| | | |
| | | .filter-section { |
| | | background: white; |
| | | padding: 20px; |
| | | border-radius: 8px; |
| | | margin-bottom: 20px; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .filter-form { |
| | | margin: 0; |
| | | } |
| | | |
| | | .table-section { |
| | | background: white; |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .batch-actions { |
| | | background: white; |
| | | padding: 15px 20px; |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | |
| | | :deep(.el-form-item__label) { |
| | | font-weight: 500; |
| | | color: #303133; |
| | | } |
| | | |
| | | :deep(.el-input__wrapper) { |
| | | box-shadow: 0 0 0 1px #dcdfe6 inset; |
| | | } |
| | | |
| | | :deep(.el-input__wrapper:hover) { |
| | | box-shadow: 0 0 0 1px #c0c4cc inset; |
| | | } |
| | | |
| | | :deep(.el-input__wrapper.is-focus) { |
| | | box-shadow: 0 0 0 1px #409eff inset; |
| | | } |
| | | |
| | | /* ååºå¼è®¾è®¡ */ |
| | | @media (max-width: 768px) { |
| | | .scheduling-container { |
| | | padding: 10px; |
| | | } |
| | | |
| | | .page-header { |
| | | padding: 15px; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | font-size: 24px; |
| | | } |
| | | |
| | | .header-controls { |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | .filter-form .el-form-item { |
| | | margin-bottom: 10px; |
| | | } |
| | | } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container self-service-container"> |
| | | |
| | | <!-- åè½å¯¼èªå¡ç --> |
| | | <el-row :gutter="20" class="nav-cards"> |
| | | <el-col :span="6" v-for="(item, index) in navItems" :key="index"> |
| | | <el-card class="nav-card" @click="handleNavClick(item.type)"> |
| | | <div class="nav-content"> |
| | | <el-icon :size="40" class="nav-icon"> |
| | | <component :is="item.icon" /> |
| | | </el-icon> |
| | | <h3>{{ item.title }}</h3> |
| | | <p>{{ item.desc }}</p> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 主è¦å
容åºå --> |
| | | <div class="main-content"> |
| | | <!-- èå¤è®°å½ --> |
| | | <el-card v-if="currentView === 'attendance'" class="content-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>个人èå¤è®°å½</span> |
| | | <el-button type="primary" @click="addAttendanceRecord">æ°å¢è®°å½</el-button> |
| | | </div> |
| | | </template> |
| | | <el-table :data="attendanceData" style="width: 100%"> |
| | | <el-table-column prop="date" label="æ¥æ" /> |
| | | <el-table-column prop="checkIn" label="ç¾å°æ¶é´" /> |
| | | <el-table-column prop="checkOut" label="ç¾éæ¶é´" /> |
| | | <el-table-column prop="workHours" label="工使¶é¿" width="100" /> |
| | | <el-table-column prop="status" label="ç¶æ" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'æ£å¸¸' ? 'success' : 'danger'"> |
| | | {{ scope.row.status }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="150"> |
| | | <template #default="scope"> |
| | | <el-button size="small" @click="editAttendanceRecord(scope.row)">ç¼è¾</el-button> |
| | | <el-button size="small" type="danger" @click="deleteAttendanceRecord(scope.$index)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | |
| | | <!-- èªèµå --> |
| | | <el-card v-if="currentView === 'salary'" class="content-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>èªèµåæ¥è¯¢</span> |
| | | <el-date-picker v-model="salaryMonth" type="month" placeholder="éæ©æä»½" /> |
| | | </div> |
| | | </template> |
| | | <el-table :data="salaryData" style="width: 100%"> |
| | | <el-table-column prop="month" label="æä»½" /> |
| | | <el-table-column prop="basicSalary" label="åºæ¬å·¥èµ" /> |
| | | <el-table-column prop="bonus" label="å¥é" /> |
| | | <el-table-column prop="deduction" label="æ£æ¬¾" /> |
| | | <el-table-column prop="total" label="å®åå·¥èµ" /> |
| | | <el-table-column prop="status" label="ç¶æ" > |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === '已忾' ? 'success' : 'warning'"> |
| | | {{ scope.row.status }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | |
| | | <!-- åæç³è¯· --> |
| | | <el-card v-if="currentView === 'leave'" class="content-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>åæç³è¯·ç®¡ç</span> |
| | | <el-button type="primary" @click="showLeaveDialog = true">ç³è¯·åæ</el-button> |
| | | </div> |
| | | </template> |
| | | <el-table :data="leaveData" style="width: 100%"> |
| | | <el-table-column prop="type" label="åæç±»å" /> |
| | | <el-table-column prop="startDate" label="å¼å§æ¥æ" /> |
| | | <el-table-column prop="endDate" label="ç»ææ¥æ" /> |
| | | <el-table-column prop="days" label="天æ°" width="80" /> |
| | | <el-table-column prop="reason" label="ç³è¯·åå " /> |
| | | <el-table-column prop="status" label="审æ¹ç¶æ" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getStatusType(scope.row.status)"> |
| | | {{ scope.row.status }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="150"> |
| | | <template #default="scope"> |
| | | <el-button size="small" @click="editLeaveRecord(scope.row)">ç¼è¾</el-button> |
| | | <el-button size="small" type="danger" @click="deleteLeaveRecord(scope.$index)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | |
| | | <!-- ä¸ªäººä¿¡æ¯ --> |
| | | <el-card v-if="currentView === 'profile'" class="content-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>个人信æ¯ç»´æ¤</span> |
| | | <el-button type="primary" @click="editProfile = true">ç¼è¾ä¿¡æ¯</el-button> |
| | | </div> |
| | | </template> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions-item label="å§å">{{ profile.name }}</el-descriptions-item> |
| | | <el-descriptions-item label="å·¥å·">{{ profile.employeeId }}</el-descriptions-item> |
| | | <el-descriptions-item label="é¨é¨">{{ profile.department }}</el-descriptions-item> |
| | | <el-descriptions-item label="èä½">{{ profile.position }}</el-descriptions-item> |
| | | <el-descriptions-item label="å
¥èæ¥æ">{{ profile.hireDate }}</el-descriptions-item> |
| | | <el-descriptions-item label="èç³»çµè¯">{{ profile.phone }}</el-descriptions-item> |
| | | <el-descriptions-item label="é®ç®±">{{ profile.email }}</el-descriptions-item> |
| | | <el-descriptions-item label="å°å">{{ profile.address }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | </el-card> |
| | | </div> |
| | | |
| | | <!-- åæç³è¯·å¼¹çª --> |
| | | <el-dialog v-model="showLeaveDialog" title="ç³è¯·åæ" width="500px"> |
| | | <el-form :model="leaveForm" label-width="100px"> |
| | | <el-form-item label="åæç±»å"> |
| | | <el-select v-model="leaveForm.type" placeholder="è¯·éæ©åæç±»å"> |
| | | <el-option label="å¹´å" value="å¹´å" /> |
| | | <el-option label="ç
å" value="ç
å" /> |
| | | <el-option label="è°ä¼" value="è°ä¼" /> |
| | | <el-option label="äºå" value="äºå" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="å¼å§æ¥æ"> |
| | | <el-date-picker v-model="leaveForm.startDate" type="date" placeholder="éæ©å¼å§æ¥æ" /> |
| | | </el-form-item> |
| | | <el-form-item label="ç»ææ¥æ"> |
| | | <el-date-picker v-model="leaveForm.endDate" type="date" placeholder="éæ©ç»ææ¥æ" /> |
| | | </el-form-item> |
| | | <el-form-item label="ç³è¯·åå "> |
| | | <el-input v-model="leaveForm.reason" type="textarea" rows="3" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="showLeaveDialog = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitLeaveApplication">æäº¤ç³è¯·</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- æ°å¢èå¤è®°å½å¼¹çª --> |
| | | <el-dialog v-model="showAttendanceDialog" title="æ°å¢èå¤è®°å½" width="500px"> |
| | | <el-form :model="attendanceForm" :rules="attendanceRules" ref="attendanceFormRef" label-width="100px"> |
| | | <el-form-item label="æ¥æ" prop="date"> |
| | | <el-date-picker v-model="attendanceForm.date" type="date" placeholder="éæ©æ¥æ" /> |
| | | </el-form-item> |
| | | <el-form-item label="ç¾å°æ¶é´" prop="checkIn"> |
| | | <el-time-picker v-model="attendanceForm.checkIn" placeholder="éæ©ç¾å°æ¶é´" format="HH:mm" value-format="HH:mm" /> |
| | | </el-form-item> |
| | | <el-form-item label="ç¾éæ¶é´" prop="checkOut"> |
| | | <el-time-picker v-model="attendanceForm.checkOut" placeholder="éæ©ç¾éæ¶é´" format="HH:mm" value-format="HH:mm" /> |
| | | </el-form-item> |
| | | <el-form-item label="ç¶æ" prop="status"> |
| | | <el-select v-model="attendanceForm.status" placeholder="è¯·éæ©ç¶æ"> |
| | | <el-option label="æ£å¸¸" value="æ£å¸¸" /> |
| | | <el-option label="è¿å°" value="è¿å°" /> |
| | | <el-option label="æ©é" value="æ©é" /> |
| | | <el-option label="缺å¤" value="缺å¤" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="showAttendanceDialog = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitAttendance">æäº¤</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 个人信æ¯ç¼è¾å¼¹çª --> |
| | | <el-dialog v-model="editProfile" title="ç¼è¾ä¸ªäººä¿¡æ¯" width="500px"> |
| | | <el-form :model="profileForm" label-width="100px"> |
| | | <el-form-item label="å§å"> |
| | | <el-input v-model="profileForm.name" /> |
| | | </el-form-item> |
| | | <el-form-item label="èç³»çµè¯"> |
| | | <el-input v-model="profileForm.phone" /> |
| | | </el-form-item> |
| | | <el-form-item label="é®ç®±"> |
| | | <el-input v-model="profileForm.email" /> |
| | | </el-form-item> |
| | | <el-form-item label="å°å"> |
| | | <el-input v-model="profileForm.address" type="textarea" rows="2" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="editProfile = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="saveProfile">ä¿å</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, watch } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { |
| | | Calendar, |
| | | Money, |
| | | Clock, |
| | | User |
| | | } from '@element-plus/icons-vue' |
| | | |
| | | // å½åè§å¾ |
| | | const currentView = ref('attendance') |
| | | |
| | | // 导èªé¡¹ |
| | | const navItems = [ |
| | | { type: 'attendance', title: 'èå¤è®°å½', desc: 'æ¥è¯¢ä¸ªäººèå¤ä¿¡æ¯', icon: 'Calendar' }, |
| | | { type: 'salary', title: 'èªèµå', desc: 'æ¥çèªèµåæ¾è®°å½', icon: 'Money' }, |
| | | { type: 'leave', title: 'åæç³è¯·', desc: 'å¨çº¿ç³è¯·åç±»åæ', icon: 'Clock' }, |
| | | { type: 'profile', title: '个人信æ¯', desc: 'ç»´æ¤ä¸ªäººåºæ¬ä¿¡æ¯', icon: 'User' } |
| | | ] |
| | | |
| | | // è夿°æ® |
| | | const attendanceData = ref([ |
| | | { date: '2024-01-15', checkIn: '09:00', checkOut: '18:00', workHours: '9å°æ¶', status: 'æ£å¸¸' }, |
| | | { date: '2024-01-16', checkIn: '08:55', checkOut: '18:05', workHours: '9å°æ¶10å', status: 'æ£å¸¸' }, |
| | | { date: '2024-01-17', checkIn: '09:15', checkOut: '18:00', workHours: '8å°æ¶45å', status: 'è¿å°' } |
| | | ]) |
| | | |
| | | // èªèµæ°æ® |
| | | const salaryData = ref([ |
| | | { month: '2024-01', basicSalary: 8000, bonus: 1000, deduction: 200, total: 8800, status: '已忾' }, |
| | | { month: '2023-12', basicSalary: 8000, bonus: 800, deduction: 150, total: 8650, status: '已忾' } |
| | | ]) |
| | | |
| | | // åææ°æ® |
| | | const leaveData = ref([ |
| | | { type: 'å¹´å', startDate: '2024-02-01', endDate: '2024-02-03', days: 3, reason: 'æ¥èåå®¶', status: 'å·²éè¿' }, |
| | | { type: 'ç
å', startDate: '2024-01-20', endDate: '2024-01-21', days: 2, reason: 'æååç§', status: '审æ¹ä¸' } |
| | | ]) |
| | | |
| | | // ä¸ªäººä¿¡æ¯ |
| | | const profile = ref({ |
| | | name: 'å¼ æµ·æ´', |
| | | employeeId: 'EMP001', |
| | | department: 'ææ¯é¨', |
| | | position: '软件工ç¨å¸', |
| | | hireDate: '2023-03-01', |
| | | phone: '13800138000', |
| | | email: 'zhangsan@company.com', |
| | | address: 'åäº¬å¸æé³åºxxxè¡éxxxå·' |
| | | }) |
| | | |
| | | // å¼¹çªæ§å¶ |
| | | const showLeaveDialog = ref(false) |
| | | const editProfile = ref(false) |
| | | const salaryMonth = ref('') |
| | | |
| | | // è¡¨åæ°æ® |
| | | const leaveForm = reactive({ |
| | | type: '', |
| | | startDate: '', |
| | | endDate: '', |
| | | reason: '' |
| | | }) |
| | | |
| | | const profileForm = reactive({ |
| | | name: '', |
| | | phone: '', |
| | | email: '', |
| | | address: '' |
| | | }) |
| | | |
| | | // æ°å¢èå¤è®°å½ï¼å¼¹çªä¸è¡¨å |
| | | const showAttendanceDialog = ref(false) |
| | | const attendanceFormRef = ref(null) |
| | | const attendanceForm = reactive({ |
| | | date: '', |
| | | checkIn: '', |
| | | checkOut: '', |
| | | status: 'æ£å¸¸' |
| | | }) |
| | | const attendanceRules = { |
| | | date: [{ required: true, message: 'è¯·éæ©æ¥æ', trigger: 'change' }], |
| | | checkIn: [{ required: true, message: 'è¯·éæ©ç¾å°æ¶é´', trigger: 'change' }], |
| | | checkOut: [{ required: true, message: 'è¯·éæ©ç¾éæ¶é´', trigger: 'change' }], |
| | | status: [{ required: true, message: 'è¯·éæ©ç¶æ', trigger: 'change' }] |
| | | } |
| | | |
| | | // å¤ç导èªç¹å» |
| | | const handleNavClick = (type) => { |
| | | currentView.value = type |
| | | } |
| | | |
| | | // è·åç¶æç±»å |
| | | const getStatusType = (status) => { |
| | | const types = { |
| | | 'å·²éè¿': 'success', |
| | | '审æ¹ä¸': 'warning', |
| | | 'å·²æç»': 'danger' |
| | | } |
| | | return types[status] || 'info' |
| | | } |
| | | |
| | | // æ°å¢èå¤è®°å½ï¼æå¼å¼¹çªå¹¶é¢å¡«é»è®¤å¼ï¼ |
| | | const addAttendanceRecord = () => { |
| | | attendanceForm.date = new Date().toISOString().split('T')[0] |
| | | attendanceForm.checkIn = '09:00' |
| | | attendanceForm.checkOut = '18:00' |
| | | attendanceForm.status = 'æ£å¸¸' |
| | | showAttendanceDialog.value = true |
| | | } |
| | | |
| | | // 计ç®å·¥æ¶ |
| | | const computeWorkHours = (inStr, outStr) => { |
| | | const [inH, inM] = inStr.split(':').map(n => parseInt(n, 10)) |
| | | const [outH, outM] = outStr.split(':').map(n => parseInt(n, 10)) |
| | | const inMin = inH * 60 + inM |
| | | const outMin = outH * 60 + outM |
| | | const diff = Math.max(0, outMin - inMin) |
| | | const h = Math.floor(diff / 60) |
| | | const m = diff % 60 |
| | | return m === 0 ? `${h}å°æ¶` : `${h}å°æ¶${m}å` |
| | | } |
| | | |
| | | // æäº¤æ°å¢èå¤è®°å½ |
| | | const submitAttendance = () => { |
| | | if (!attendanceFormRef.value) return |
| | | attendanceFormRef.value.validate((valid) => { |
| | | if (!valid) return |
| | | const workHours = computeWorkHours(attendanceForm.checkIn, attendanceForm.checkOut) |
| | | const newRecord = { |
| | | date: attendanceForm.date, |
| | | checkIn: attendanceForm.checkIn, |
| | | checkOut: attendanceForm.checkOut, |
| | | workHours, |
| | | status: attendanceForm.status |
| | | } |
| | | attendanceData.value.unshift(newRecord) |
| | | showAttendanceDialog.value = false |
| | | // é置表å |
| | | attendanceForm.date = '' |
| | | attendanceForm.checkIn = '' |
| | | attendanceForm.checkOut = '' |
| | | attendanceForm.status = 'æ£å¸¸' |
| | | ElMessage.success('èå¤è®°å½æ·»å æå') |
| | | }) |
| | | } |
| | | |
| | | // ç¼è¾èå¤è®°å½ |
| | | const editAttendanceRecord = (row) => { |
| | | ElMessage.info('ç¼è¾åè½å¼åä¸...') |
| | | } |
| | | |
| | | // å é¤èå¤è®°å½ |
| | | const deleteAttendanceRecord = (index) => { |
| | | attendanceData.value.splice(index, 1) |
| | | ElMessage.success('èå¤è®°å½å 餿å') |
| | | } |
| | | |
| | | // ç¼è¾åæè®°å½ |
| | | const editLeaveRecord = (row) => { |
| | | ElMessage.info('ç¼è¾åè½å¼åä¸...') |
| | | } |
| | | |
| | | // å é¤åæè®°å½ |
| | | const deleteLeaveRecord = (index) => { |
| | | leaveData.value.splice(index, 1) |
| | | ElMessage.success('åæè®°å½å 餿å') |
| | | } |
| | | |
| | | // æäº¤åæç³è¯· |
| | | const submitLeaveApplication = () => { |
| | | if (!leaveForm.type || !leaveForm.startDate || !leaveForm.endDate || !leaveForm.reason) { |
| | | ElMessage.warning('请填å宿´ä¿¡æ¯') |
| | | return |
| | | } |
| | | |
| | | const newLeave = { |
| | | type: leaveForm.type, |
| | | startDate: leaveForm.startDate, |
| | | endDate: leaveForm.endDate, |
| | | days: 3, // ç®åè®¡ç® |
| | | reason: leaveForm.reason, |
| | | status: '审æ¹ä¸' |
| | | } |
| | | |
| | | leaveData.value.unshift(newLeave) |
| | | showLeaveDialog.value = false |
| | | |
| | | // é置表å |
| | | Object.keys(leaveForm).forEach(key => { |
| | | leaveForm[key] = '' |
| | | }) |
| | | |
| | | ElMessage.success('åæç³è¯·æäº¤æå') |
| | | } |
| | | |
| | | // ä¿åä¸ªäººä¿¡æ¯ |
| | | const saveProfile = () => { |
| | | Object.assign(profile.value, profileForm) |
| | | editProfile.value = false |
| | | ElMessage.success('个人信æ¯ä¿åæå') |
| | | } |
| | | |
| | | // åå§å个人信æ¯è¡¨å |
| | | const initProfileForm = () => { |
| | | Object.assign(profileForm, { |
| | | name: profile.value.name, |
| | | phone: profile.value.phone, |
| | | email: profile.value.email, |
| | | address: profile.value.address |
| | | }) |
| | | } |
| | | |
| | | // çå¬ç¼è¾ä¸ªäººä¿¡æ¯å¼¹çª |
| | | watch(editProfile, (val) => { |
| | | if (val) { |
| | | initProfileForm() |
| | | } |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .self-service-container { |
| | | padding: 20px; |
| | | background-color: #f5f7fa; |
| | | min-height: 100vh; |
| | | } |
| | | |
| | | .page-header { |
| | | text-align: center; |
| | | margin-bottom: 30px; |
| | | padding: 20px; |
| | | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | | border-radius: 12px; |
| | | color: white; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | color: white; |
| | | margin-bottom: 10px; |
| | | font-size: 28px; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .page-header p { |
| | | color: rgba(255, 255, 255, 0.9); |
| | | font-size: 14px; |
| | | margin: 0; |
| | | } |
| | | |
| | | .nav-cards { |
| | | margin-bottom: 30px; |
| | | } |
| | | |
| | | .nav-card { |
| | | cursor: pointer; |
| | | transition: all 0.3s ease; |
| | | border-radius: 12px; |
| | | border: none; |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .nav-card:hover { |
| | | transform: translateY(-5px); |
| | | box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); |
| | | } |
| | | |
| | | .nav-content { |
| | | text-align: center; |
| | | padding: 20px; |
| | | } |
| | | |
| | | .nav-icon { |
| | | color: #409EFF; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .nav-content h3 { |
| | | margin: 0 0 10px 0; |
| | | color: #303133; |
| | | font-size: 18px; |
| | | } |
| | | |
| | | .nav-content p { |
| | | margin: 0; |
| | | color: #909399; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .main-content { |
| | | margin-bottom: 30px; |
| | | } |
| | | |
| | | .content-card { |
| | | border-radius: 12px; |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
| | | border: none; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | |
| | | /* ååºå¼è®¾è®¡ */ |
| | | @media (max-width: 768px) { |
| | | .self-service-container { |
| | | padding: 10px; |
| | | } |
| | | |
| | | .nav-cards .el-col { |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | font-size: 24px; |
| | | } |
| | | } |
| | | </style> |