From abb1971cd5eeab76736dcf402f804098929ac7bc Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期二, 17 三月 2026 16:59:06 +0800
Subject: [PATCH] 军泰伟业app 1.添加营销管理模块和采购管理模块并联调 2.添加协同办公模块

---
 src/pages/sales/deliveryLedger/index.vue |  785 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 785 insertions(+), 0 deletions(-)

diff --git a/src/pages/sales/deliveryLedger/index.vue b/src/pages/sales/deliveryLedger/index.vue
new file mode 100644
index 0000000..e968aa9
--- /dev/null
+++ b/src/pages/sales/deliveryLedger/index.vue
@@ -0,0 +1,785 @@
+<template>
+  <view class="delivery-ledger">
+    <!-- 椤甸潰澶撮儴 -->
+    <PageHeader title="鍙戣揣鍙拌处" @back="goBack" />
+
+    <!-- 鎼滅储鍖哄煙 -->
+    <view class="search-section">
+      <view class="search-bar">
+        <view class="search-input">
+          <up-input
+            class="search-text"
+            placeholder="璇疯緭鍏ラ攢鍞鍗曞彿"
+            v-model="searchForm.salesContractNo"
+            @change="handleQuery"
+            clearable
+          />
+        </view>
+        <view class="search-input">
+          <up-input
+            class="search-text"
+            placeholder="璇疯緭鍏ュ鎴峰悕绉�"
+            v-model="searchForm.customerName"
+            @change="handleQuery"
+            clearable
+          />
+        </view>
+        <view class="filter-button" @click="handleQuery">
+          <up-icon name="search" size="24" color="#999"></up-icon>
+        </view>
+      </view>
+    </view>
+
+    <!-- 鍙戣揣鍙拌处鍒楄〃 -->
+    <view class="ledger-list" v-if="tableData.length > 0">
+      <view v-for="(item, index) in tableData" :key="index">
+        <view class="ledger-item" @click="openDetail(item)">
+          <view class="item-header">
+            <view class="item-left">
+              <view class="document-icon">
+                <up-icon name="file-text" size="16" color="#ffffff"></up-icon>
+              </view>
+              <text class="item-id">{{ item.salesContractNo }}</text>
+            </view>
+            <u-tag
+              size="mini"
+              :type="getApprovalStatusType(item.status)"
+            >{{ getApprovalStatusText(item.status) }}</u-tag>
+          </view>
+          <up-divider></up-divider>
+          <view class="item-details">
+            <view class="detail-row">
+              <text class="detail-label">鍙戣揣璁㈠崟鍙�</text>
+              <text class="detail-value">{{ item.shippingNo || '--' }}</text>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">瀹㈡埛鍚嶇О</text>
+              <text class="detail-value">{{ item.customerName }}</text>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">鍙戣揣杩涘害</text>
+              <view class="progress-wrapper">
+                <up-line-progress
+                  :percentage="getShippingProgress(item)"
+                  :active-color="getProgressColor(item)"
+                  height="8"
+                ></up-line-progress>
+                <text class="progress-text">{{ getShippingProgress(item) }}%</text>
+              </view>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">鎬诲彂璐ф暟閲�</text>
+              <text class="detail-value">{{ item.shippingTotal || 0 }}</text>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">宸插彂璐ф暟閲�</text>
+              <text class="detail-value success">{{ item.shippingSuccessTotal || 0 }}</text>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">寰呭彂璐ф暟閲�</text>
+              <text class="detail-value warning">{{ item.waitShippingTotal || 0 }}</text>
+            </view>
+          </view>
+        </view>
+      </view>
+    </view>
+    <view v-else class="no-data">
+      <up-empty mode="list" text="鏆傛棤鍙戣揣鍙拌处鏁版嵁"></up-empty>
+    </view>
+
+    <!-- 鍙戣揣璇︽儏寮圭獥 -->
+    <u-popup
+      :show="dialogFormVisible"
+      mode="bottom"
+      :round="16"
+      :closeable="true"
+      @close="closeDia"
+    >
+      <view class="popup-content">
+        <view class="popup-header">
+          <text class="popup-title">鍙戣揣鍙拌处璇︽儏</text>
+        </view>
+        <scroll-view scroll-y class="popup-body">
+          <view v-if="currentShippingOrder" class="shipping-container">
+            <!-- 璁㈠崟淇℃伅鍗$墖 -->
+            <view class="info-card">
+              <view class="card-header">
+                <text class="card-title">璁㈠崟淇℃伅</text>
+                <u-tag
+                  size="mini"
+                  :type="getProgressColor(currentShippingOrder) === '#67C23A' ? 'success' : 'warning'"
+                >{{ getShippingProgress(currentShippingOrder) }}%</u-tag>
+              </view>
+              <view class="card-body">
+                <view class="info-row">
+                  <text class="info-label">閿�鍞鍗�</text>
+                  <text class="info-value">{{ currentShippingOrder.salesContractNo || '--' }}</text>
+                </view>
+                <view class="info-row">
+                  <text class="info-label">瀹㈡埛鍚嶇О</text>
+                  <text class="info-value">{{ currentShippingOrder.customerName || '--' }}</text>
+                </view>
+                <view class="info-row">
+                  <text class="info-label">鍙戣揣璁㈠崟鍙�</text>
+                  <text class="info-value">{{ currentShippingOrder.shippingNo || '--' }}</text>
+                </view>
+                <up-divider></up-divider>
+                <view class="quantity-summary">
+                  <view class="summary-item">
+                    <text class="summary-label">鎬诲彂璐ф暟閲�</text>
+                    <text class="summary-value total">{{ currentShippingOrder.shippingTotal || 0 }}</text>
+                  </view>
+                  <view class="summary-item">
+                    <text class="summary-label">宸插彂璐ф暟閲�</text>
+                    <text class="summary-value shipped">{{ currentShippingOrder.shippingSuccessTotal || 0 }}</text>
+                  </view>
+                  <view class="summary-item">
+                    <text class="summary-label">寰呭彂璐ф暟閲�</text>
+                    <text class="summary-value waiting">{{ currentShippingOrder.waitShippingTotal || 0 }}</text>
+                  </view>
+                </view>
+                <view class="progress-wrapper">
+                  <up-line-progress
+                    :percentage="getShippingProgress(currentShippingOrder)"
+                    :active-color="getProgressColor(currentShippingOrder)"
+                    height="12"
+                  ></up-line-progress>
+                </view>
+              </view>
+            </view>
+
+            <!-- 鍙戣揣璁板綍鍗$墖 -->
+            <view class="records-card">
+              <view class="card-header">
+                <text class="card-title">鍙戣揣璁板綍</text>
+                <text class="record-count">鍏� {{ shippingRecords.length }} 鏉¤褰�</text>
+              </view>
+              <view class="card-body">
+                <view v-if="shippingRecords.length === 0" class="empty-state">
+                  <up-empty mode="list" text="鏆傛棤鍙戣揣璁板綍"></up-empty>
+                </view>
+                <view v-else class="records-list">
+                  <view
+                    v-for="(record, index) in shippingRecords"
+                    :key="record.id || index"
+                    class="record-item"
+                  >
+                    <view class="record-header">
+                      <u-tag
+                        size="mini"
+                        :type="record.type === '璐ц溅' ? 'primary' : 'success'"
+                      >{{ record.type }}</u-tag>
+                      <text class="record-date">{{ record.shippingDate }}</text>
+                    </view>
+                    <view class="record-body">
+                      <view class="record-info-row">
+                        <text class="record-info-label">鍙戣揣鏁伴噺</text>
+                        <text class="record-info-value quantity">{{ record.shippingNum }}</text>
+                      </view>
+                      <view class="record-info-row" v-if="record.type === '璐ц溅'">
+                        <text class="record-info-label">杞︾墝鍙�</text>
+                        <text class="record-info-value">{{ record.shippingCarNumber || '--' }}</text>
+                      </view>
+                      <view class="record-info-row" v-else>
+                        <text class="record-info-label">蹇�掑叕鍙�</text>
+                        <text class="record-info-value">{{ record.expressCompany || '--' }}</text>
+                      </view>
+                      <view class="record-info-row" v-if="record.type === '蹇��'">
+                        <text class="record-info-label">蹇�掑崟鍙�</text>
+                        <text class="record-info-value">{{ record.expressNumber || '--' }}</text>
+                      </view>
+                      <view class="record-info-row" v-if="record.commonFileList && record.commonFileList.length > 0">
+                        <text class="record-info-label">鍙戣揣鍥剧墖</text>
+                        <view class="record-images">
+                          <image
+                            v-for="(file, imgIndex) in record.commonFileList"
+                            :key="imgIndex"
+                            :src="normalizeFileUrl(file?.url)"
+                            mode="aspectFill"
+                            class="record-image"
+                            @click="previewImage(record.commonFileList, imgIndex)"
+                          />
+                        </view>
+                      </view>
+                    </view>
+                  </view>
+                </view>
+              </view>
+            </view>
+          </view>
+        </scroll-view>
+        <view class="popup-footer">
+          <u-button @click="closeDia">鍏抽棴</u-button>
+        </view>
+      </view>
+    </u-popup>
+  </view>
+</template>
+
+<script setup>
+import { ref, reactive } from "vue";
+import { onShow } from "@dcloudio/uni-app";
+import PageHeader from "@/components/PageHeader.vue";
+import {
+  deliveryLedgerListPage,
+  shippingInfoDetailListPage,
+} from "@/api/salesManagement/deliveryLedger.js";
+
+// 琛ㄦ牸鏁版嵁
+const tableData = ref([]);
+const tableLoading = ref(false);
+const page = reactive({
+  current: 1,
+  size: 100,
+});
+const total = ref(0);
+const javaApi = import.meta.env.VITE_APP_BASE_API;
+
+// 鎼滅储琛ㄥ崟
+const searchForm = reactive({
+  salesContractNo: "",
+  customerName: "",
+});
+
+// 鍙戣揣璇︽儏寮规
+const dialogFormVisible = ref(false);
+const currentShippingOrder = ref(null);
+const shippingRecords = ref([]);
+const shippingRecordsLoading = ref(false);
+
+// 杩斿洖涓婁竴椤�
+const goBack = () => {
+  uni.navigateBack();
+};
+
+// 鏄剧ず鍔犺浇鎻愮ず
+const showLoadingToast = (message) => {
+  uni.showLoading({
+    title: message,
+    mask: true,
+  });
+};
+
+// 鍏抽棴鎻愮ず
+const closeToast = () => {
+  uni.hideLoading();
+};
+
+// 鏌ヨ鍒楄〃
+const handleQuery = () => {
+  page.current = 1;
+  getList();
+};
+
+const getList = () => {
+  tableLoading.value = true;
+  showLoadingToast("鍔犺浇涓�...");
+  deliveryLedgerListPage({ ...searchForm, ...page })
+    .then((res) => {
+      tableData.value = res.data.records || [];
+      total.value = res.data.total || 0;
+      closeToast();
+      tableLoading.value = false;
+    })
+    .catch(() => {
+      closeToast();
+      tableLoading.value = false;
+    });
+};
+
+// 鎵撳紑璇︽儏寮规
+const openDetail = async (row) => {
+  currentShippingOrder.value = row;
+  await loadShippingRecords(row.id);
+  dialogFormVisible.value = true;
+};
+
+// 鍔犺浇鍙戣揣璁板綍
+const loadShippingRecords = async (shippingInfoId) => {
+  shippingRecordsLoading.value = true;
+  try {
+    const res = await shippingInfoDetailListPage({ shippingInfoId, current: 1, size: 100 });
+    shippingRecords.value = res.data.records || [];
+  } catch (error) {
+    shippingRecords.value = [];
+  } finally {
+    shippingRecordsLoading.value = false;
+  }
+};
+
+// 鍏抽棴寮规
+const closeDia = () => {
+  dialogFormVisible.value = false;
+  currentShippingOrder.value = null;
+  shippingRecords.value = [];
+};
+
+// 鏂囦欢URL澶勭悊
+const normalizeFileUrl = (rawUrl = '') => {
+  let fileUrl = rawUrl || '';
+  if (fileUrl && fileUrl.indexOf('\\') > -1) {
+    const uploadsIndex = fileUrl.toLowerCase().indexOf('uploads');
+    if (uploadsIndex > -1) {
+      const relativePath = fileUrl.substring(uploadsIndex).replace(/\\/g, '/');
+      fileUrl = '/' + relativePath;
+    } else {
+      const parts = fileUrl.split('\\');
+      const fileName = parts[parts.length - 1];
+      fileUrl = '/uploads/' + fileName;
+    }
+  }
+  if (fileUrl && !fileUrl.startsWith('http')) {
+    if (!fileUrl.startsWith('/')) fileUrl = '/' + fileUrl;
+    fileUrl = javaApi + fileUrl;
+  }
+  return fileUrl;
+};
+
+// 棰勮鍥剧墖
+const previewImage = (fileList, currentIndex) => {
+  const urls = fileList.map((f) => normalizeFileUrl(f?.url));
+  uni.previewImage({
+    urls: urls,
+    current: currentIndex,
+  });
+};
+
+// 鑾峰彇鍙戣揣杩涘害鐧惧垎姣�
+const getShippingProgress = (row) => {
+  const shipped = row.shippingSuccessTotal || 0;
+  const total = shipped + (row.waitShippingTotal || 0);
+  if (total === 0) return 0;
+  return Math.round((shipped / total) * 100);
+};
+
+// 鑾峰彇杩涘害鏉¢鑹�
+const getProgressColor = (row) => {
+  const progress = getShippingProgress(row);
+  if (progress === 100) return '#67C23A';
+  if (progress >= 50) return '#E6A23C';
+  return '#2979ff';
+};
+
+// 鑾峰彇瀹℃牳鐘舵�佹枃鏈�
+const getApprovalStatusText = (status) => {
+  if (status === null || status === undefined || status === '') {
+    return '寰呭鏍�';
+  }
+  if (typeof status === 'number') {
+    const statusMap = {
+      0: '寰呭鏍�',
+      1: '瀹℃牳涓�',
+      2: '瀹℃牳鎷掔粷',
+      3: '瀹℃牳閫氳繃'
+    };
+    return statusMap[status] || '寰呭鏍�';
+  }
+  const statusStr = String(status).trim();
+  const statusTextMap = {
+    '寰呭鏍�': '寰呭鏍�',
+    '瀹℃牳涓�': '瀹℃牳涓�',
+    '瀹℃牳鎷掔粷': '瀹℃牳鎷掔粷',
+    '瀹℃牳閫氳繃': '瀹℃牳閫氳繃',
+    '0': '寰呭鏍�',
+    '1': '瀹℃牳涓�',
+    '2': '瀹℃牳鎷掔粷',
+    '3': '瀹℃牳閫氳繃'
+  };
+  return statusTextMap[statusStr] || statusStr || '寰呭鏍�';
+};
+
+// 鑾峰彇瀹℃牳鐘舵�佹爣绛剧被鍨�
+const getApprovalStatusType = (status) => {
+  if (status === null || status === undefined || status === '') {
+    return 'info';
+  }
+  if (typeof status === 'number') {
+    const typeMap = {
+      0: 'info',
+      1: 'warning',
+      2: 'error',
+      3: 'success'
+    };
+    return typeMap[status] || 'info';
+  }
+  const statusStr = String(status).trim();
+  const typeTextMap = {
+    '寰呭鏍�': 'info',
+    '瀹℃牳涓�': 'warning',
+    '瀹℃牳鎷掔粷': 'error',
+    '瀹℃牳閫氳繃': 'success',
+    '0': 'info',
+    '1': 'warning',
+    '2': 'error',
+    '3': 'success'
+  };
+  return typeTextMap[statusStr] || 'info';
+};
+
+onShow(() => {
+  getList();
+});
+</script>
+
+<style scoped lang="scss">
+.delivery-ledger {
+  min-height: 100vh;
+  background: #f8f9fa;
+  position: relative;
+}
+
+// 鎼滅储鍖哄煙
+.search-section {
+  padding: 10px 20px;
+  background: #ffffff;
+}
+
+.search-bar {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  flex-wrap: wrap;
+}
+
+.search-input {
+  flex: 1;
+  min-width: 140px;
+  background: #f5f5f5;
+  border-radius: 24px;
+  padding: 0 16px;
+  display: flex;
+  align-items: center;
+}
+
+.search-text {
+  flex: 1;
+  font-size: 14px;
+  color: #333;
+  background: transparent;
+  border: none;
+  outline: none;
+
+  &::placeholder {
+    color: #999;
+  }
+}
+
+.filter-button {
+  width: 40px;
+  height: 40px;
+  border-radius: 8px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #f5f5f5;
+}
+
+// 鍒楄〃鍖哄煙
+.ledger-list {
+  padding: 20px;
+}
+
+.ledger-item {
+  background: #ffffff;
+  border-radius: 12px;
+  margin-bottom: 16px;
+  overflow: hidden;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+  padding: 0 16px;
+
+  &:active {
+    transform: scale(0.98);
+    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
+  }
+}
+
+.item-header {
+  padding: 16px 0;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.item-left {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.document-icon {
+  width: 24px;
+  height: 24px;
+  background: #2979ff;
+  border-radius: 4px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.item-id {
+  font-size: 14px;
+  color: #333;
+  font-weight: 500;
+}
+
+.item-details {
+  padding: 16px 0;
+}
+
+.detail-row {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 8px;
+
+  &:last-child {
+    margin-bottom: 0;
+  }
+}
+
+.detail-label {
+  font-size: 12px;
+  color: #777777;
+  min-width: 60px;
+}
+
+.detail-value {
+  font-size: 12px;
+  color: #000000;
+  text-align: right;
+  flex: 1;
+  margin-left: 16px;
+
+  &.success {
+    color: #67C23A;
+    font-weight: 500;
+  }
+
+  &.warning {
+    color: #E6A23C;
+    font-weight: 500;
+  }
+}
+
+.progress-wrapper {
+  flex: 1;
+  margin-left: 16px;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+
+  .progress-text {
+    font-size: 12px;
+    color: #666;
+    min-width: 36px;
+    text-align: right;
+  }
+}
+
+.no-data {
+  padding: 40px 20px;
+  text-align: center;
+  color: #999;
+}
+
+// 寮圭獥鏍峰紡
+.popup-content {
+  max-height: 80vh;
+  display: flex;
+  flex-direction: column;
+}
+
+.popup-header {
+  padding: 16px;
+  border-bottom: 1px solid #eee;
+  text-align: center;
+
+  .popup-title {
+    font-size: 16px;
+    font-weight: 500;
+    color: #333;
+  }
+}
+
+.popup-body {
+  flex: 1;
+  padding: 16px;
+  max-height: 60vh;
+}
+
+.popup-footer {
+  padding: 16px;
+  border-top: 1px solid #eee;
+  display: flex;
+  gap: 12px;
+
+  :deep(.u-button) {
+    flex: 1;
+  }
+}
+
+// 鍗$墖鏍峰紡
+.info-card,
+.records-card {
+  background: #ffffff;
+  border-radius: 12px;
+  margin-bottom: 16px;
+  overflow: hidden;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+}
+
+.card-header {
+  padding: 16px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  border-bottom: 1px solid #f5f5f5;
+
+  .card-title {
+    font-size: 14px;
+    font-weight: 500;
+    color: #333;
+  }
+
+  .record-count {
+    font-size: 12px;
+    color: #999;
+  }
+}
+
+.card-body {
+  padding: 16px;
+}
+
+// 璁㈠崟淇℃伅
+.info-row {
+  display: flex;
+  justify-content: space-between;
+  padding: 8px 0;
+
+  .info-label {
+    color: #666;
+    font-size: 14px;
+  }
+
+  .info-value {
+    color: #333;
+    font-weight: 500;
+    font-size: 14px;
+  }
+}
+
+.quantity-summary {
+  display: flex;
+  justify-content: space-between;
+  margin: 16px 0;
+
+  .summary-item {
+    text-align: center;
+    flex: 1;
+
+    .summary-label {
+      font-size: 12px;
+      color: #999;
+      margin-bottom: 8px;
+      display: block;
+    }
+
+    .summary-value {
+      font-size: 20px;
+      font-weight: bold;
+
+      &.total {
+        color: #2979ff;
+      }
+
+      &.shipped {
+        color: #67C23A;
+      }
+
+      &.waiting {
+        color: #E6A23C;
+      }
+    }
+  }
+}
+
+// 鍙戣揣璁板綍
+.empty-state {
+  padding: 40px 0;
+}
+
+.records-list {
+  .record-item {
+    border: 1px solid #eee;
+    border-radius: 8px;
+    padding: 12px;
+    margin-bottom: 12px;
+
+    &:last-child {
+      margin-bottom: 0;
+    }
+  }
+
+  .record-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 12px;
+    padding-bottom: 12px;
+    border-bottom: 1px solid #f5f5f5;
+
+    .record-date {
+      font-size: 12px;
+      color: #999;
+    }
+  }
+
+  .record-body {
+    .record-info-row {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      margin-bottom: 8px;
+
+      &:last-child {
+        margin-bottom: 0;
+      }
+
+      .record-info-label {
+        font-size: 12px;
+        color: #999;
+      }
+
+      .record-info-value {
+        font-size: 14px;
+        color: #333;
+
+        &.quantity {
+          font-weight: bold;
+          color: #2979ff;
+          font-size: 16px;
+        }
+      }
+    }
+
+    .record-images {
+      display: flex;
+      gap: 8px;
+      flex-wrap: wrap;
+      margin-top: 8px;
+
+      .record-image {
+        width: 60px;
+        height: 60px;
+        border-radius: 4px;
+      }
+    }
+  }
+}
+
+// 闅愯棌uview-plus鐨勬煇浜涢粯璁ゆ牱寮�
+:deep(.u-divider) {
+  margin: 8px 0 !important;
+}
+</style>

--
Gitblit v1.9.3