| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="contract-management-page"> |
| | | <PageHeader title="åå管ç" |
| | | @back="goBack" /> |
| | | <!-- æç´¢æ --> |
| | | <view class="search-section"> |
| | | <view class="search-bar"> |
| | | <view class="search-input"> |
| | | <up-input class="search-text" |
| | | placeholder="请è¾å
¥åå·¥å§åæç´¢" |
| | | v-model="searchValue" |
| | | @blur="handleSearch" |
| | | @change="handleSearch" |
| | | clearable /> |
| | | </view> |
| | | <view class="filter-button" |
| | | @click="handleSearch"> |
| | | <u-icon name="search" |
| | | size="24" |
| | | color="#999"></u-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <!-- ååä¿¡æ¯å¡ç --> |
| | | <view class="contract-card"> |
| | | <!-- 循ç¯å±ç¤ºç¨æ·å表 --> |
| | | <view v-for="(userItem, index) in userList" |
| | | :key="index" |
| | | class="user-card"> |
| | | <!-- åºæ¬ä¿¡æ¯å¡ç --> |
| | | <view class="basic-info-card"> |
| | | <view class="card-header"> |
| | | <text class="card-title">{{ userItem.staffName || '-' }}</text> |
| | | <text class="card-subtitle"><u-tag :type="userItem.staffState == '1' ? 'primary' : 'error'" |
| | | :text="userItem.staffState == '1' ? ' å¨è ' : ' 离è '"></u-tag></text> |
| | | </view> |
| | | <view class="card-content"> |
| | | <view class="info-row"> |
| | | <view class="info-item"> |
| | | <text class="info-label">åå·¥ç¼å·</text> |
| | | <text class="info-value">{{ userItem.staffNo || '-' }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="info-label">æ§å«</text> |
| | | <text class="info-value">{{ userItem.sex || '-' }}</text> |
| | | </view> |
| | | </view> |
| | | <view class="info-row"> |
| | | <view class="info-item"> |
| | | <text class="info-label">å²ä½</text> |
| | | <text class="info-value">{{ userItem.postJob || '-' }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="info-label">第ä¸å¦å</text> |
| | | <text class="info-value">{{ userItem.firstStudy || '-' }}</text> |
| | | </view> |
| | | </view> |
| | | <view class="info-row"> |
| | | <view class="info-item"> |
| | | <text class="info-label">ä¸ä¸</text> |
| | | <text class="info-value">{{ userItem.profession || '-' }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="info-label">å¹´é¾</text> |
| | | <text class="info-value">{{ userItem.age || '-' }}</text> |
| | | </view> |
| | | </view> |
| | | <view class="info-row"> |
| | | <view class="info-item"> |
| | | <text class="info-label">èç³»çµè¯</text> |
| | | <text class="info-value">{{ userItem.phone || '-' }}</text> |
| | | </view> |
| | | </view> |
| | | <!-- <view class="info-row"> |
| | | <view class="info-item"> |
| | | <text class="info-label">ç´§æ¥è系人</text> |
| | | <text class="info-value">{{ userItem.emergencyContact || '-' }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="info-label">ç´§æ¥è系人çµè¯</text> |
| | | <text class="info-value">{{ userItem.emergencyContactPhone || '-' }}</text> |
| | | </view> |
| | | </view> --> |
| | | <!-- <view class="info-row"> |
| | | <view class="info-item"> |
| | | <text class="info-label">æ·ç±ä½å</text> |
| | | <text class="info-value">{{ userItem.nativePlace || '-' }}</text> |
| | | </view> |
| | | </view> --> |
| | | <view class="info-row"> |
| | | <view class="info-item"> |
| | | <text class="info-label">ç°ä½å</text> |
| | | <text class="info-value">{{ userItem.adress || '-' }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <!-- ååå表å¡ç --> |
| | | <view class="contract-list-card"> |
| | | <view class="card-header"> |
| | | <up-button size="small" |
| | | :type="userItem.contractExpanded ? 'default' : 'primary'" |
| | | :loading="userItem.contractLoading" |
| | | @click="toggleContractExpanded(index)"> |
| | | {{ userItem.contractExpanded ? 'éèååå表' : 'ååå表' }}<u-icon v-if="userItem.contractExpanded" |
| | | name="arrow-down" |
| | | size="12" |
| | | style="margin-left: 4px;" |
| | | color="#000"></u-icon> |
| | | </up-button> |
| | | </view> |
| | | <view v-if="(userItem.contractExpanded || userItem.contractLoading) && userItem.contractList.length > 0" |
| | | class="card-content"> |
| | | <view v-if="userItem.contractLoading" |
| | | class="list-loading"> |
| | | <u-icon name="loading" |
| | | size="24" |
| | | color="#348fe2"></u-icon> |
| | | <text>å è½½ä¸...</text> |
| | | </view> |
| | | <template v-else> |
| | | <view v-for="contract in userItem.contractList" |
| | | :key="contract.id" |
| | | class="contract-item"> |
| | | <view class="contract-item-header"> |
| | | <text class="contract-name">{{ contract.contractName }}</text> |
| | | <view class="contract-status" |
| | | :class="getContractStatusClass(contract.status)"> |
| | | <text>{{ contract.status }}</text> |
| | | </view> |
| | | </view> |
| | | <view class="contract-item-content"> |
| | | <view class="contract-detail"> |
| | | <text class="detail-label">ååå¹´éï¼</text> |
| | | <text class="detail-value">{{ contract.contractTerm || '-' }}</text> |
| | | </view> |
| | | <view class="contract-detail"> |
| | | <text class="detail-label">ååå¼å§æ¥æï¼</text> |
| | | <text class="detail-value">{{ contract.contractStartTime || '-' }}</text> |
| | | </view> |
| | | <view class="contract-detail"> |
| | | <text class="detail-label">ååç»ææ¥æï¼</text> |
| | | <text class="detail-value">{{ contract.contractEndTime || '-' }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <!-- ç©ºç¶æ --> |
| | | <view v-if="!loading && userList.length === 0" |
| | | class="empty-state"> |
| | | <u-icon name="document" |
| | | size="60" |
| | | color="#999"></u-icon> |
| | | <text class="empty-text">ææ ååä¿¡æ¯</text> |
| | | </view> |
| | | </view> |
| | | <!-- å è½½ç¶æ --> |
| | | <view v-if="loading" |
| | | class="loading-state"> |
| | | <u-icon name="loading" |
| | | size="40" |
| | | color="#348fe2"></u-icon> |
| | | <text class="loading-text">å è½½ä¸...</text> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from "vue"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { |
| | | staffOnJobListPage, |
| | | findStaffContractListPage, |
| | | } from "@/api/humanResources/contractManagement"; |
| | | |
| | | // ç¨æ·ååä¿¡æ¯å表 |
| | | const userList = ref([]); |
| | | |
| | | // å è½½ç¶æ |
| | | const loading = ref(false); |
| | | |
| | | // æç´¢å¼ |
| | | const searchValue = ref(""); |
| | | |
| | | // æä»¶å表 |
| | | const fileList = ref([ |
| | | { |
| | | id: 1, |
| | | fileName: "å³å¨åå.pdf", |
| | | fileSize: "2.5MB", |
| | | uploadDate: "2026-02-01", |
| | | }, |
| | | { |
| | | id: 2, |
| | | fileName: "ä¿å¯åè®®.pdf", |
| | | fileSize: "1.8MB", |
| | | uploadDate: "2026-02-01", |
| | | }, |
| | | { |
| | | id: 3, |
| | | fileName: "ç«ä¸éå¶åè®®.pdf", |
| | | fileSize: "1.2MB", |
| | | uploadDate: "2026-02-01", |
| | | }, |
| | | ]); |
| | | |
| | | // ç¨æ·åå¨ |
| | | const userStore = useUserStore(); |
| | | |
| | | // è¿åä¸ä¸é¡µ |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | // è·åç¨æ·ååä¿¡æ¯ |
| | | const getUserContractInfo = (searchName = "") => { |
| | | loading.value = true; |
| | | const params = { |
| | | current: -1, |
| | | size: -1, |
| | | staffName: searchName || "", |
| | | }; |
| | | |
| | | staffOnJobListPage(params) |
| | | .then(response => { |
| | | // 为æ¯ä¸ªç¨æ·æ·»å contractListåcontractLoading屿§ |
| | | userList.value = response.data.records.map(user => ({ |
| | | ...user, |
| | | contractList: [], |
| | | contractLoading: false, |
| | | contractExpanded: false, |
| | | })); |
| | | }) |
| | | .catch(error => { |
| | | console.error("è·åååä¿¡æ¯å¤±è´¥:", error); |
| | | }) |
| | | .finally(() => { |
| | | loading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | // å è½½ååå表 |
| | | const loadContractList = (id, index) => { |
| | | // æ£æ¥ç¨æ·æ¯å¦åå¨ |
| | | console.log(userList.value[index], "userList.value[index]"); |
| | | if (!userList.value[index]) return; |
| | | |
| | | const userItem = userList.value[index]; |
| | | console.log(userItem.contractList.length, "userItem.contractList.length"); |
| | | // 妿已ç»å è½½è¿ï¼ä¸åéå¤å è½½ |
| | | if (userItem.contractList.length > 0) return; |
| | | |
| | | // 设置å è½½ç¶æ |
| | | userItem.contractLoading = true; |
| | | |
| | | // è°ç¨è¯¦æ
æ¥å£ |
| | | findStaffContractListPage({ staffOnJobId: id }) |
| | | .then(res => { |
| | | if (res.data && res.data.records) { |
| | | userItem.contractList = res.data.records; |
| | | } else { |
| | | userItem.contractList = []; |
| | | } |
| | | }) |
| | | .finally(() => { |
| | | // éç½®å è½½ç¶æ |
| | | userItem.contractLoading = false; |
| | | }); |
| | | }; |
| | | |
| | | // è·åååç¶ææ ·å¼ |
| | | const getContractStatusClass = status => { |
| | | switch (status) { |
| | | case "çæä¸": |
| | | return "status-active"; |
| | | case "å·²è¿æ": |
| | | return "status-expired"; |
| | | case "å·²ç»æ¢": |
| | | return "status-terminated"; |
| | | default: |
| | | return "status-default"; |
| | | } |
| | | }; |
| | | |
| | | // 忢åå详æ
å±å¼/å
³éç¶æ |
| | | const toggleContractExpanded = index => { |
| | | if (!userList.value[index]) return; |
| | | |
| | | const userItem = userList.value[index]; |
| | | |
| | | // å¦æè¿æ²¡æå è½½ååå表ï¼å
å è½½æ°æ® |
| | | if (userItem.contractList.length === 0) { |
| | | loadContractList(userItem.id, index); |
| | | // å è½½å®æåèªå¨å±å¼ |
| | | userItem.contractExpanded = true; |
| | | } else { |
| | | // 忢å±å¼/å
³éç¶æ |
| | | userItem.contractExpanded = !userItem.contractExpanded; |
| | | } |
| | | }; |
| | | |
| | | // å¤çæç´¢ |
| | | const handleSearch = () => { |
| | | getUserContractInfo(searchValue.value); |
| | | }; |
| | | |
| | | // 页é¢å è½½æ¶è·åååä¿¡æ¯ |
| | | onMounted(() => { |
| | | getUserContractInfo(""); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "../../../styles/sales-common.scss"; |
| | | // å
¨å±åé |
| | | $primary-color: #2c7be5; |
| | | $primary-light: #4a90e2; |
| | | $primary-lightest: #e8f0fe; |
| | | $text-primary: #333333; |
| | | $text-secondary: #666666; |
| | | $text-tertiary: #999999; |
| | | $bg-color: #f8f9fa; |
| | | $card-bg: #ffffff; |
| | | $border-color: #e8e8e8; |
| | | $shadow-sm: 0 2rpx 12rpx rgba(0, 0, 0, 0.08); |
| | | $shadow-md: 0 4rpx 16rpx rgba(0, 0, 0, 0.12); |
| | | $border-radius: 16rpx; |
| | | |
| | | .contract-management-page { |
| | | min-height: 100vh; |
| | | background-color: $bg-color; |
| | | padding-bottom: 40rpx; |
| | | } |
| | | |
| | | /* ååå¡ç */ |
| | | .contract-card { |
| | | margin: 24rpx; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 24rpx; |
| | | } |
| | | |
| | | /* åºæ¬ä¿¡æ¯å¡ç */ |
| | | .basic-info-card { |
| | | background-color: $card-bg; |
| | | border-radius: $border-radius $border-radius 0 0; |
| | | box-shadow: $shadow-md; |
| | | overflow: hidden; |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | /* ååå表å¡ç */ |
| | | .contract-list-card { |
| | | background-color: $card-bg; |
| | | border-radius: 0 0 $border-radius $border-radius; |
| | | box-shadow: $shadow-md; |
| | | overflow: hidden; |
| | | border-top: 1rpx solid $border-color; |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | /* ç¨æ·å¡ç */ |
| | | .user-card { |
| | | margin-bottom: 0; |
| | | border-radius: $border-radius; |
| | | overflow: hidden; |
| | | box-shadow: $shadow-md; |
| | | transition: transform 0.3s ease, box-shadow 0.3s ease; |
| | | } |
| | | |
| | | .user-card:hover { |
| | | transform: translateY(-4rpx); |
| | | box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.15); |
| | | } |
| | | |
| | | /* å¡çå¤´é¨ */ |
| | | .card-header { |
| | | padding: 24rpx; |
| | | border-bottom: 1rpx solid $border-color; |
| | | background-color: $primary-lightest; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .card-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: $primary-color; |
| | | } |
| | | |
| | | .card-subtitle { |
| | | font-size: 13px; |
| | | color: $text-secondary; |
| | | padding: 4rpx 12rpx; |
| | | border-radius: 12rpx; |
| | | } |
| | | |
| | | /* å¡çå
容 */ |
| | | .card-content { |
| | | padding: 24rpx; |
| | | } |
| | | |
| | | /* ä¿¡æ¯è¡ */ |
| | | .info-row { |
| | | display: flex; |
| | | margin-bottom: 20rpx; |
| | | gap: 20rpx; |
| | | } |
| | | |
| | | .info-row:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | /* ä¿¡æ¯é¡¹ */ |
| | | .info-item { |
| | | flex: 1; |
| | | display: flex; |
| | | flex-direction: column; |
| | | padding: 12rpx; |
| | | background-color: #f9fafb; |
| | | border-radius: 12rpx; |
| | | border: 1rpx solid $border-color; |
| | | } |
| | | |
| | | /* ä¿¡æ¯æ ç¾ */ |
| | | .info-label { |
| | | font-size: 12px; |
| | | color: $text-secondary; |
| | | margin-bottom: 6rpx; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | /* ä¿¡æ¯å¼ */ |
| | | .info-value { |
| | | font-size: 14px; |
| | | color: $text-primary; |
| | | font-weight: 500; |
| | | line-height: 1.4; |
| | | } |
| | | |
| | | /* æä»¶è¡¨æ ¼ */ |
| | | .file-table { |
| | | width: 100%; |
| | | } |
| | | |
| | | /* è¡¨æ ¼å¤´é¨ */ |
| | | .table-header { |
| | | display: flex; |
| | | padding: 12rpx 0; |
| | | border-bottom: 1rpx solid $border-color; |
| | | font-weight: 600; |
| | | font-size: 13px; |
| | | color: $text-secondary; |
| | | } |
| | | |
| | | /* è¡¨æ ¼è¡ */ |
| | | .table-row { |
| | | display: flex; |
| | | padding: 16rpx 0; |
| | | border-bottom: 1rpx solid $border-color; |
| | | font-size: 13px; |
| | | color: $text-primary; |
| | | } |
| | | |
| | | .table-row:last-child { |
| | | border-bottom: none; |
| | | } |
| | | |
| | | /* è¡¨æ ¼å */ |
| | | .table-col { |
| | | flex: 1; |
| | | text-align: center; |
| | | } |
| | | |
| | | /* æä»¶åç§°å */ |
| | | .file-name { |
| | | flex: 2; |
| | | text-align: left; |
| | | } |
| | | |
| | | /* è¡¨æ ¼ç©ºç¶æ */ |
| | | .table-empty { |
| | | padding: 40rpx 0; |
| | | text-align: center; |
| | | color: $text-tertiary; |
| | | font-size: 13px; |
| | | } |
| | | |
| | | /* ååå表 */ |
| | | .contract-list { |
| | | padding: 16rpx; |
| | | } |
| | | |
| | | /* åå项 */ |
| | | .contract-item { |
| | | background-color: #f9fafb; |
| | | border-radius: 12rpx; |
| | | padding: 16rpx; |
| | | |
| | | border-left: 4px solid $primary-color; |
| | | } |
| | | |
| | | .contract-item:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | /* ååé¡¹å¤´é¨ */ |
| | | .contract-item-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 12rpx; |
| | | } |
| | | |
| | | /* åååç§° */ |
| | | .contract-name { |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: $text-primary; |
| | | } |
| | | |
| | | /* ååç¶æ */ |
| | | .contract-status { |
| | | padding: 4rpx 12rpx; |
| | | border-radius: 12rpx; |
| | | font-size: 12px; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | /* ååç¶ææ ·å¼ */ |
| | | .status-active { |
| | | background-color: #e6f7ee; |
| | | color: #389e75; |
| | | } |
| | | |
| | | .status-expired { |
| | | background-color: #fff5f5; |
| | | color: #dc3545; |
| | | } |
| | | |
| | | .status-terminated { |
| | | background-color: #f8f9fa; |
| | | color: #6c757d; |
| | | } |
| | | |
| | | .status-default { |
| | | background-color: #e9ecef; |
| | | color: #495057; |
| | | } |
| | | |
| | | /* åå项å
容 */ |
| | | .contract-item-content { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 8rpx; |
| | | } |
| | | |
| | | /* åå详æ
*/ |
| | | .contract-detail { |
| | | display: flex; |
| | | font-size: 13px; |
| | | } |
| | | |
| | | /* 详æ
æ ç¾ */ |
| | | .detail-label { |
| | | color: $text-secondary; |
| | | margin-right: 8rpx; |
| | | } |
| | | |
| | | /* 详æ
å¼ */ |
| | | .detail-value { |
| | | color: $text-primary; |
| | | flex: 1; |
| | | } |
| | | |
| | | /* åè¡¨ç©ºç¶æ */ |
| | | .list-empty { |
| | | padding: 40rpx 0; |
| | | text-align: center; |
| | | color: $text-tertiary; |
| | | font-size: 13px; |
| | | } |
| | | |
| | | /* å表å è½½ç¶æ */ |
| | | .list-loading { |
| | | padding: 40rpx 0; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | gap: 12rpx; |
| | | color: $text-secondary; |
| | | font-size: 13px; |
| | | } |
| | | |
| | | /* å è½½ç¶æ */ |
| | | .loading-state { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 100rpx 0; |
| | | } |
| | | |
| | | .loading-text { |
| | | font-size: 14px; |
| | | color: $text-tertiary; |
| | | margin-top: 20rpx; |
| | | } |
| | | |
| | | /* ç©ºç¶æ */ |
| | | .empty-state { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 120rpx 0; |
| | | text-align: center; |
| | | } |
| | | |
| | | .empty-text { |
| | | font-size: 14px; |
| | | color: $text-tertiary; |
| | | margin-top: 24rpx; |
| | | } |
| | | </style> |