From 1ed366885433dfdec1241312356535b868c39eee Mon Sep 17 00:00:00 2001
From: zhangwencui <1064582902@qq.com>
Date: 星期四, 26 二月 2026 16:21:04 +0800
Subject: [PATCH] 合同管理模块开发

---
 src/pages/index.vue                                   |   10 
 src/pages.json                                        |    7 
 src/api/humanResources/contractManagement.js          |   16 +
 src/pages/humanResources/contractManagement/index.vue |  630 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 663 insertions(+), 0 deletions(-)

diff --git a/src/api/humanResources/contractManagement.js b/src/api/humanResources/contractManagement.js
new file mode 100644
index 0000000..5555fe7
--- /dev/null
+++ b/src/api/humanResources/contractManagement.js
@@ -0,0 +1,16 @@
+import request from '@/utils/request'
+// 鏌ヨ鍦ㄨ亴鍛樺伐鍙拌处
+export function staffOnJobListPage(query) {
+    return request({
+        url: '/staff/staffOnJob/listPage',
+        method: 'get',
+        params: query,
+    })
+}
+export function findStaffContractListPage(query) {
+    return request({
+        url: "/staff/staffContract/listPage",
+        method: "get",
+        params: query,
+    });
+}
diff --git a/src/pages.json b/src/pages.json
index 0772b2e..c39d3b9 100644
--- a/src/pages.json
+++ b/src/pages.json
@@ -886,6 +886,13 @@
       }
     },
     {
+      "path": "pages/humanResources/contractManagement/index",
+      "style": {
+        "navigationBarTitleText": "鍚堝悓绠$悊",
+        "navigationStyle": "custom"
+      }
+    },
+    {
       "path": "pages/message",
       "style": {
         "navigationBarTitleText": "娑堟伅涓績"
diff --git a/src/pages/humanResources/contractManagement/index.vue b/src/pages/humanResources/contractManagement/index.vue
new file mode 100644
index 0000000..b036ed6
--- /dev/null
+++ b/src/pages/humanResources/contractManagement/index.vue
@@ -0,0 +1,630 @@
+<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 => {
+        // 涓烘瘡涓敤鎴锋坊鍔燾ontractList鍜宑ontractLoading灞炴��
+        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>
\ No newline at end of file
diff --git a/src/pages/index.vue b/src/pages/index.vue
index 1b20903..d1f8f1a 100644
--- a/src/pages/index.vue
+++ b/src/pages/index.vue
@@ -309,6 +309,10 @@
       icon: "/static/images/icon/caigoutaizhang@2x.png",
       label: "浜哄憳钖祫",
     },
+    {
+      icon: "/static/images/icon/caigoutaizhang@2x.png",
+      label: "鍚堝悓绠$悊",
+    },
   ]);
   const safetyItems = reactive([
     {
@@ -716,6 +720,11 @@
           url: "/pages/humanResources/monthlyStatistics/index",
         });
         break;
+      case "鍚堝悓绠$悊":
+        uni.navigateTo({
+          url: "/pages/humanResources/contractManagement/index",
+        });
+        break;
       default:
         uni.showToast({
           title: `鐐瑰嚮浜�${item.label}`,
@@ -981,6 +990,7 @@
     const originalHumanResources = [
       { icon: "/static/images/icon/caigoutaizhang@2x.png", label: "鎵撳崱绛惧埌" },
       { icon: "/static/images/icon/caigoutaizhang@2x.png", label: "浜哄憳钖祫" },
+      { icon: "/static/images/icon/caigoutaizhang@2x.png", label: "鍚堝悓绠$悊" },
     ];
     const filteredHumanResources = originalHumanResources.filter(item => {
       return allowedMenuTitles.has(item.label);

--
Gitblit v1.9.3