From e170706bd4f620170ef60c13a06a59a81156a069 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期一, 30 三月 2026 11:25:06 +0800
Subject: [PATCH] 金鹰黄金app 1.客户修改

---
 src/api/basicData/customerFile.js      |   41 +
 src/pages/sales/customerFile/index.vue | 1544 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 1,585 insertions(+), 0 deletions(-)

diff --git a/src/api/basicData/customerFile.js b/src/api/basicData/customerFile.js
index c52b76e..409161a 100644
--- a/src/api/basicData/customerFile.js
+++ b/src/api/basicData/customerFile.js
@@ -50,3 +50,44 @@
     })
 }
 
+
+// 鏂板瀹㈡埛璺熻繘
+export function addCustomerFollow(data) {
+    return request({
+        url: '/basic/customer-follow/add',
+        method: 'post',
+        data: data
+    })
+}
+
+// 淇敼瀹㈡埛璺熻繘
+export function updateCustomerFollow(data) {
+    return request({
+        url: '/basic/customer-follow/edit',
+        method: 'put',
+        data: data,
+    })
+}
+// 鍒犻櫎瀹㈡埛璺熻繘
+export function delCustomerFollow(id) {
+    return request({
+        url: '/basic/customer-follow/'+id,
+        method: 'delete',
+    })
+}
+
+// 鍥炶鎻愰啋-鏂板/鏇存柊
+export function addReturnVisit(data) {
+    return request({
+        url: '/basic/customer-follow/return-visit',
+        method: 'post',
+        data: data
+    })
+}
+// 鑾峰彇鍥炶鎻愰啋璇︽儏
+export function getReturnVisit(id) {
+    return request({
+        url: '/basic/customer-follow/return-visit/' + id,
+        method: 'get'
+    })
+}
\ No newline at end of file
diff --git a/src/pages/sales/customerFile/index.vue b/src/pages/sales/customerFile/index.vue
new file mode 100644
index 0000000..04017b5
--- /dev/null
+++ b/src/pages/sales/customerFile/index.vue
@@ -0,0 +1,1544 @@
+<template>
+  <view class="app-container">
+    <!-- 鎼滅储鍖哄煙 -->
+    <view class="search-section">
+      <view class="search-row">
+        <up-input
+          v-model="searchForm.customerName"
+          placeholder="璇疯緭鍏ュ鎴峰悕绉�"
+          prefixIcon="search"
+          :customStyle="{ marginBottom: '10rpx' }"
+          @confirm="handleQuery"
+        />
+      </view>
+      <view class="search-row">
+        <up-input
+          v-model="searchForm.customerType"
+          placeholder="璇烽�夋嫨瀹㈡埛鍒嗙被"
+          suffixIcon="arrow-down"
+          readonly
+          @click="showTypePicker = true"
+        />
+      </view>
+      <view class="search-actions">
+        <up-button type="primary" size="small" @click="handleQuery">鎼滅储</up-button>
+        <up-button size="small" @click="resetSearch">閲嶇疆</up-button>
+      </view>
+    </view>
+
+    <!-- 鎿嶄綔鎸夐挳 -->
+    <view class="action-bar">
+      <up-button type="primary" size="small" icon="plus" @click="openForm('add')">鏂板</up-button>
+      <up-button size="small" icon="download" @click="handleOut">瀵煎嚭</up-button>
+      <up-button type="info" size="small" icon="upload" @click="handleImport">瀵煎叆</up-button>
+      <up-button type="error" size="small" icon="trash" @click="handleDelete">鍒犻櫎</up-button>
+    </view>
+
+    <!-- 瀹㈡埛鍒楄〃 -->
+    <scroll-view class="customer-list" scroll-y @scrolltolower="loadMore">
+      <view
+        v-for="item in tableData"
+        :key="item.id"
+        class="customer-card"
+        @click="openDetailDialog(item)"
+      >
+        <view class="card-header">
+          <text class="customer-name">{{ item.customerName }}</text>
+          <up-tag :text="item.customerType" type="primary" size="mini" />
+        </view>
+        <view class="card-body">
+          <view class="info-row">
+            <text class="label">鑱旂郴浜猴細</text>
+            <text class="value">{{ item.contactPerson || '-' }}</text>
+          </view>
+          <view class="info-row">
+            <text class="label">鑱旂郴鐢佃瘽锛�</text>
+            <text class="value">{{ item.contactPhone || '-' }}</text>
+          </view>
+          <view class="info-row">
+            <text class="label">璺熻繘杩涘害锛�</text>
+            <text class="value">{{ item.followUpLevel || '-' }}</text>
+          </view>
+          <view class="info-row">
+            <text class="label">璺熻繘鏃堕棿锛�</text>
+            <text class="value">{{ item.followUpTime || '-' }}</text>
+          </view>
+          <view class="info-row">
+            <text class="label">缁存姢浜猴細</text>
+            <text class="value">{{ item.maintainer || '-' }}</text>
+          </view>
+        </view>
+        <view class="card-footer">
+          <up-button type="primary" text="缂栬緫" size="mini" @click.stop="openForm('edit', item)" />
+          <up-button type="warning" text="娲借皥杩涘害" size="mini" @click.stop="openNegotiationDialog(item)" />
+          <up-button type="success" text="鍥炶鎻愰啋" size="mini" @click.stop="openReminderDialog(item)" />
+        </view>
+      </view>
+      <up-loadmore :status="loadStatus" />
+    </scroll-view>
+
+    <!-- 瀹㈡埛鍒嗙被閫夋嫨鍣� -->
+    <up-picker
+      :show="showTypePicker"
+      :columns="[customerTypeOptions]"
+      @confirm="onTypeConfirm"
+      @cancel="showTypePicker = false"
+    />
+
+    <!-- 鏂板/缂栬緫瀹㈡埛寮圭獥 -->
+    <up-popup :show="dialogFormVisible" mode="bottom" :round="10" @close="closeDia">
+      <view class="popup-header">
+        <text class="popup-title">{{ operationType === 'add' ? '鏂板瀹㈡埛淇℃伅' : '缂栬緫瀹㈡埛淇℃伅' }}</text>
+        <up-icon name="close" size="20" @click="closeDia" />
+      </view>
+      <scroll-view class="popup-body" scroll-y>
+        <up-form :model="form" :rules="rules" ref="formRef" labelPosition="top">
+          <up-form-item label="瀹㈡埛鍚嶇О" prop="customerName" required>
+            <up-input v-model="form.customerName" placeholder="璇疯緭鍏�" />
+          </up-form-item>
+          <up-form-item label="绾崇◣浜鸿瘑鍒彿" prop="taxpayerIdentificationNumber" required>
+            <up-input v-model="form.taxpayerIdentificationNumber" placeholder="璇疯緭鍏�" />
+          </up-form-item>
+          <up-form-item label="鍏徃鍦板潃" prop="companyAddress" required>
+            <up-input v-model="form.companyAddress" placeholder="璇疯緭鍏�" />
+          </up-form-item>
+          <up-form-item label="鍏徃鐢佃瘽" prop="companyPhone" required>
+            <up-input v-model="form.companyPhone" placeholder="璇疯緭鍏�" />
+          </up-form-item>
+          <up-form-item label="閾惰鍩烘湰鎴�" prop="basicBankAccount" required>
+            <up-input v-model="form.basicBankAccount" placeholder="璇疯緭鍏�" />
+          </up-form-item>
+          <up-form-item label="閾惰璐﹀彿" prop="bankAccount" required>
+            <up-input v-model="form.bankAccount" placeholder="璇疯緭鍏�" />
+          </up-form-item>
+          <up-form-item label="寮�鎴疯鍙�" prop="bankCode" required>
+            <up-input v-model="form.bankCode" placeholder="璇疯緭鍏�" />
+          </up-form-item>
+          <up-form-item label="瀹㈡埛鍒嗙被" prop="customerType" required>
+            <up-input
+              v-model="form.customerType"
+              placeholder="璇烽�夋嫨"
+              suffixIcon="arrow-down"
+              readonly
+              @click="showFormTypePicker = true"
+            />
+          </up-form-item>
+          <view v-for="(contact, index) in formYYs.contactList" :key="index" class="contact-section">
+            <up-form-item :label="`鑱旂郴浜�${index + 1}`">
+              <up-input v-model="contact.contactPerson" placeholder="璇疯緭鍏�" />
+            </up-form-item>
+            <up-form-item :label="`鑱旂郴鐢佃瘽${index + 1}`">
+              <view class="contact-phone-row">
+                <up-input v-model="contact.contactPhone" placeholder="璇疯緭鍏�" />
+                <up-button type="error" size="mini" icon="minus" @click="removeContact(index)" />
+              </view>
+            </up-form-item>
+          </view>
+          <up-button type="primary" plain size="small" icon="plus" @click="addNewContact" style="margin-bottom: 20rpx;">
+            鏂板鑱旂郴浜�
+          </up-button>
+          <up-form-item label="缁存姢浜�" prop="maintainer">
+            <up-input v-model="form.maintainer" disabled />
+          </up-form-item>
+          <up-form-item label="缁存姢鏃堕棿" prop="maintenanceTime">
+            <up-input
+              v-model="form.maintenanceTime"
+              placeholder="璇烽�夋嫨"
+              suffixIcon="calendar"
+              readonly
+              @click="showDatePicker = true"
+            />
+          </up-form-item>
+        </up-form>
+      </scroll-view>
+      <view class="popup-footer">
+        <up-button type="primary" @click="submitForm">纭</up-button>
+        <up-button @click="closeDia">鍙栨秷</up-button>
+      </view>
+    </up-popup>
+
+    <!-- 瀹㈡埛鍒嗙被閫夋嫨鍣紙琛ㄥ崟鍐咃級 -->
+    <up-picker
+      :show="showFormTypePicker"
+      :columns="[customerTypeOptions]"
+      @confirm="onFormTypeConfirm"
+      @cancel="showFormTypePicker = false"
+    />
+
+    <!-- 鏃ユ湡閫夋嫨鍣� -->
+    <up-datetime-picker
+      :show="showDatePicker"
+      v-model="datePickerValue"
+      mode="date"
+      @confirm="onDateConfirm"
+      @cancel="showDatePicker = false"
+    />
+
+    <!-- 瀵煎叆寮圭獥 -->
+    <up-popup :show="upload.open" mode="center" :round="10" @close="upload.open = false">
+      <view class="popup-content" style="width: 600rpx; padding: 30rpx;">
+        <text class="popup-title">{{ upload.title }}</text>
+        <view class="upload-area" @click="chooseFile">
+          <up-icon name="upload" size="48" color="#909399" />
+          <text class="upload-text">鐐瑰嚮閫夋嫨鏂囦欢</text>
+          <text class="upload-tip">浠呮敮鎸� xls銆亁lsx 鏍煎紡</text>
+        </view>
+        <text v-if="upload.fileName" class="file-name">宸查�夋嫨: {{ upload.fileName }}</text>
+        <view class="popup-footer">
+          <up-button type="primary" size="small" @click="submitFileForm">纭畾</up-button>
+          <up-button size="small" @click="upload.open = false">鍙栨秷</up-button>
+        </view>
+        <up-button type="primary" text="涓嬭浇妯℃澘" size="mini" plain @click="importTemplate" style="margin-top: 20rpx;" />
+      </view>
+    </up-popup>
+
+    <!-- 鍥炶鎻愰啋寮圭獥 -->
+    <up-popup :show="reminderDialogVisible" mode="bottom" :round="10" @close="closeReminderDialog">
+      <view class="popup-header">
+        <text class="popup-title">鍥炶鎻愰啋</text>
+        <up-icon name="close" size="20" @click="closeReminderDialog" />
+      </view>
+      <view class="popup-body">
+        <up-form :model="reminderForm" :rules="reminderRules" ref="reminderFormRef" labelPosition="top">
+          <up-form-item label="瀹㈡埛鍚嶇О">
+            <up-input v-model="reminderForm.customerName" disabled />
+          </up-form-item>
+          <up-form-item label="鎻愰啋寮�鍏�">
+            <up-switch v-model="reminderForm.reminderSwitch" />
+          </up-form-item>
+          <up-form-item label="鎻愰啋鍐呭" prop="reminderContent">
+            <up-textarea v-model="reminderForm.reminderContent" placeholder="璇疯緭鍏ユ彁閱掑唴瀹�" maxlength="100" count />
+          </up-form-item>
+          <up-form-item label="鎻愰啋鏃堕棿" prop="reminderTime">
+            <up-input
+              v-model="reminderForm.reminderTime"
+              placeholder="璇烽�夋嫨鎻愰啋鏃堕棿"
+              suffixIcon="clock"
+              readonly
+              @click="showReminderTimePicker = true"
+            />
+          </up-form-item>
+        </up-form>
+      </view>
+      <view class="popup-footer">
+        <up-button type="primary" @click="submitReminderForm">纭</up-button>
+        <up-button @click="closeReminderDialog">鍙栨秷</up-button>
+      </view>
+    </up-popup>
+
+    <!-- 鎻愰啋鏃堕棿閫夋嫨鍣� -->
+    <up-datetime-picker
+      :show="showReminderTimePicker"
+      v-model="reminderTimeValue"
+      mode="datetime"
+      @confirm="onReminderTimeConfirm"
+      @cancel="showReminderTimePicker = false"
+    />
+
+    <!-- 娲借皥杩涘害寮圭獥 -->
+    <up-popup :show="negotiationDialogVisible" mode="bottom" :round="10" @close="closeNegotiationDialog">
+      <view class="popup-header">
+        <text class="popup-title">{{ negotiationForm.editIndex !== undefined ? '淇敼杩涘害' : '娣诲姞杩涘害' }}</text>
+        <up-icon name="close" size="20" @click="closeNegotiationDialog" />
+      </view>
+      <view class="popup-body">
+        <up-form :model="negotiationForm" :rules="negotiationRules" ref="negotiationFormRef" labelPosition="top">
+          <up-form-item label="璺熻繘鏂瑰紡" prop="followUpMethod" required>
+            <up-input
+              v-model="negotiationForm.followUpMethod"
+              placeholder="璇烽�夋嫨"
+              suffixIcon="arrow-down"
+              readonly
+              @click="showFollowMethodPicker = true"
+            />
+          </up-form-item>
+          <up-form-item label="璺熻繘绋嬪害" prop="followUpLevel" required>
+            <up-input
+              v-model="negotiationForm.followUpLevel"
+              placeholder="璇烽�夋嫨"
+              suffixIcon="arrow-down"
+              readonly
+              @click="showFollowLevelPicker = true"
+            />
+          </up-form-item>
+          <up-form-item label="璺熻繘鏃堕棿" prop="followUpTime" required>
+            <up-input
+              v-model="negotiationForm.followUpTime"
+              placeholder="璇烽�夋嫨"
+              suffixIcon="clock"
+              readonly
+              @click="showFollowTimePicker = true"
+            />
+          </up-form-item>
+          <up-form-item label="璺熻繘浜�">
+            <up-input v-model="negotiationForm.followerUserName" disabled />
+          </up-form-item>
+          <up-form-item label="鍐呭" prop="content" required>
+            <up-textarea v-model="negotiationForm.content" placeholder="璇疯緭鍏�" :rows="4" />
+          </up-form-item>
+        </up-form>
+      </view>
+      <view class="popup-footer">
+        <up-button type="primary" @click="submitNegotiationForm">纭</up-button>
+        <up-button @click="closeNegotiationDialog">鍙栨秷</up-button>
+      </view>
+    </up-popup>
+
+    <!-- 璺熻繘鏂瑰紡閫夋嫨鍣� -->
+    <up-picker
+      :show="showFollowMethodPicker"
+      :columns="[followMethodOptions]"
+      @confirm="onFollowMethodConfirm"
+      @cancel="showFollowMethodPicker = false"
+    />
+
+    <!-- 璺熻繘绋嬪害閫夋嫨鍣� -->
+    <up-picker
+      :show="showFollowLevelPicker"
+      :columns="[followLevelOptions]"
+      @confirm="onFollowLevelConfirm"
+      @cancel="showFollowLevelPicker = false"
+    />
+
+    <!-- 璺熻繘鏃堕棿閫夋嫨鍣� -->
+    <up-datetime-picker
+      :show="showFollowTimePicker"
+      v-model="followTimeValue"
+      mode="datetime"
+      @confirm="onFollowTimeConfirm"
+      @cancel="showFollowTimePicker = false"
+    />
+
+    <!-- 瀹㈡埛璇︽儏寮圭獥 -->
+    <up-popup :show="detailDialogVisible" mode="bottom" :round="10" @close="closeDetailDialog">
+      <view class="popup-header">
+        <text class="popup-title">瀹㈡埛璇︽儏</text>
+        <up-icon name="close" size="20" @click="closeDetailDialog" />
+      </view>
+      <scroll-view class="popup-body" scroll-y style="max-height: 70vh;">
+        <!-- 瀹㈡埛鍩烘湰淇℃伅 -->
+        <view class="detail-section">
+          <view class="section-header">
+            <text class="section-title">瀹㈡埛鍩烘湰淇℃伅</text>
+          </view>
+          <view class="info-list">
+            <view class="info-item">
+              <text class="info-label">瀹㈡埛鍚嶇О锛�</text>
+              <text class="info-value">{{ detailForm.customerName }}</text>
+            </view>
+            <view class="info-item">
+              <text class="info-label">瀹㈡埛鍒嗙被锛�</text>
+              <text class="info-value">{{ detailForm.customerType }}</text>
+            </view>
+            <view class="info-item">
+              <text class="info-label">绾崇◣浜鸿瘑鍒彿锛�</text>
+              <text class="info-value">{{ detailForm.taxpayerIdentificationNumber }}</text>
+            </view>
+            <view class="info-item">
+              <text class="info-label">鍏徃鐢佃瘽锛�</text>
+              <text class="info-value">{{ detailForm.companyPhone }}</text>
+            </view>
+            <view class="info-item">
+              <text class="info-label">鍏徃鍦板潃锛�</text>
+              <text class="info-value">{{ detailForm.companyAddress }}</text>
+            </view>
+            <view class="info-item">
+              <text class="info-label">閾惰鍩烘湰鎴凤細</text>
+              <text class="info-value">{{ detailForm.basicBankAccount }}</text>
+            </view>
+            <view class="info-item">
+              <text class="info-label">閾惰璐﹀彿锛�</text>
+              <text class="info-value">{{ detailForm.bankAccount }}</text>
+            </view>
+            <view class="info-item">
+              <text class="info-label">寮�鎴疯鍙凤細</text>
+              <text class="info-value">{{ detailForm.bankCode }}</text>
+            </view>
+            <view class="info-item">
+              <text class="info-label">鑱旂郴浜猴細</text>
+              <text class="info-value">{{ detailForm.contactPerson }}</text>
+            </view>
+            <view class="info-item">
+              <text class="info-label">鑱旂郴鐢佃瘽锛�</text>
+              <text class="info-value">{{ detailForm.contactPhone }}</text>
+            </view>
+            <view class="info-item">
+              <text class="info-label">缁存姢浜猴細</text>
+              <text class="info-value">{{ detailForm.maintainer }}</text>
+            </view>
+            <view class="info-item">
+              <text class="info-label">缁存姢鏃堕棿锛�</text>
+              <text class="info-value">{{ detailForm.maintenanceTime }}</text>
+            </view>
+          </view>
+        </view>
+
+        <!-- 娲借皥杩涘害璁板綍 -->
+        <view class="detail-section">
+          <view class="section-header">
+            <text class="section-title">娲借皥杩涘害璁板綍</text>
+            <up-button type="primary" size="mini" @click="openNegotiationDialog(detailForm)">娣诲姞杩涘害</up-button>
+          </view>
+          <view v-if="negotiationRecords.length === 0" class="empty-text">鏆傛棤璁板綍</view>
+          <view v-for="(record, index) in negotiationRecords" :key="index" class="record-card">
+            <view class="record-header">
+              <text class="record-time">{{ record.followUpTime }}</text>
+              <up-tag :text="record.followUpMethod" type="info" size="mini" />
+            </view>
+            <view class="record-body">
+              <text class="record-level">{{ record.followUpLevel }}</text>
+              <text class="record-content">{{ record.content }}</text>
+            </view>
+            <view class="record-footer">
+              <text class="record-user">璺熻繘浜猴細{{ record.followerUserName }}</text>
+              <view class="record-actions">
+                <up-button type="primary" text="闄勪欢" size="mini" @click="openAttachmentDialog(record)" />
+                <up-button type="warning" text="缂栬緫" size="mini" @click="editNegotiationRecord(record, index)" />
+                <up-button type="error" text="鍒犻櫎" size="mini" @click="deleteNegotiationRecord(record, index)" />
+              </view>
+            </view>
+          </view>
+        </view>
+      </scroll-view>
+    </up-popup>
+
+    <!-- 闄勪欢寮圭獥 -->
+    <up-popup :show="attachmentDialogVisible" mode="bottom" :round="10" @close="closeAttachmentDialog">
+      <view class="popup-header">
+        <text class="popup-title">闄勪欢绠$悊</text>
+        <up-icon name="close" size="20" @click="closeAttachmentDialog" />
+      </view>
+      <scroll-view class="popup-body" scroll-y style="max-height: 60vh;">
+        <view v-if="currentAttachmentList.length === 0" class="empty-text">鏆傛棤闄勪欢</view>
+        <view v-for="(file, index) in currentAttachmentList" :key="index" class="file-item">
+          <view class="file-info">
+            <up-icon name="file-text" size="24" />
+            <view class="file-detail">
+              <text class="file-name">{{ file.name }}</text>
+              <text class="file-size">{{ formatFileSize(file.size) }}</text>
+            </view>
+          </view>
+          <view class="file-actions">
+            <up-button type="primary" size="mini" text="涓嬭浇" @click="downloadAttachment(file)" />
+            <up-button type="error" size="mini" text="鍒犻櫎" @click="deleteAttachment(file, index)" />
+          </view>
+        </view>
+      </scroll-view>
+      <view class="popup-footer">
+        <up-button type="primary" @click="chooseAttachment">涓婁紶闄勪欢</up-button>
+        <up-button @click="closeAttachmentDialog">鍏抽棴</up-button>
+      </view>
+    </up-popup>
+  </view>
+</template>
+
+<script setup>
+import { onMounted, ref, reactive, getCurrentInstance, toRefs } from 'vue';
+import {
+  addCustomer,
+  delCustomer,
+  getCustomer,
+  listCustomer,
+  updateCustomer,
+  addCustomerFollow,
+  updateCustomerFollow,
+  delCustomerFollow,
+  addReturnVisit,
+  getReturnVisit,
+} from '@/api/basicData/customerFile.js';
+import { userListNoPage } from '@/api/system/user.js';
+import useUserStore from '@/store/modules/user';
+import { getToken } from '@/utils/auth.js';
+
+const { proxy } = getCurrentInstance();
+const userStore = useUserStore();
+
+// 鎼滅储鐩稿叧
+const showTypePicker = ref(false);
+const customerTypeOptions = ['闆跺敭瀹㈡埛', '杩涢攢鍟嗗鎴�'];
+
+// 琛ㄥ崟鐩稿叧
+const showFormTypePicker = ref(false);
+const showDatePicker = ref(false);
+const datePickerValue = ref(Date.now());
+
+// 鍥炶鎻愰啋鐩稿叧
+const reminderDialogVisible = ref(false);
+const reminderFormRef = ref();
+const currentCustomerId = ref();
+const showReminderTimePicker = ref(false);
+const reminderTimeValue = ref(Date.now());
+const reminderForm = reactive({
+  customerName: '',
+  reminderSwitch: false,
+  reminderContent: '',
+  reminderTime: '',
+});
+const reminderRules = {
+  reminderContent: [
+    { required: true, message: '璇疯緭鍏ユ彁閱掑唴瀹�', trigger: 'blur' },
+  ],
+  reminderTime: [
+    { required: true, message: '璇烽�夋嫨鎻愰啋鏃堕棿', trigger: 'change' },
+  ],
+};
+
+// 娲借皥杩涘害鐩稿叧
+const negotiationDialogVisible = ref(false);
+const negotiationFormRef = ref();
+const showFollowMethodPicker = ref(false);
+const showFollowLevelPicker = ref(false);
+const showFollowTimePicker = ref(false);
+const followTimeValue = ref(Date.now());
+const followMethodOptions = ['鐢佃瘽', '閭欢', '涓婇棬', '寰俊', '鍏朵粬'];
+const followLevelOptions = ['娼滃湪瀹㈡埛', '鍒濇鎷滆', '澶氭鎷滆', '鎰忓悜瀹㈡埛', '宸茬绾﹀鎴�'];
+const negotiationForm = reactive({
+  customerName: '',
+  customerId: '',
+  followUpMethod: '',
+  followUpLevel: '',
+  followUpTime: '',
+  followerUserName: '',
+  content: '',
+});
+const negotiationRules = {
+  followUpMethod: [
+    { required: true, message: '璇烽�夋嫨璺熻繘鏂瑰紡', trigger: 'change' },
+  ],
+  followUpLevel: [
+    { required: true, message: '璇烽�夋嫨璺熻繘绋嬪害', trigger: 'change' },
+  ],
+  followUpTime: [
+    { required: true, message: '璇烽�夋嫨璺熻繘鏃堕棿', trigger: 'change' },
+  ],
+  content: [{ required: true, message: '璇疯緭鍏ュ唴瀹�', trigger: 'blur' }],
+};
+
+// 璇︽儏鐩稿叧
+const detailDialogVisible = ref(false);
+const detailForm = reactive({
+  customerName: '',
+  customerType: '',
+  taxpayerIdentificationNumber: '',
+  companyPhone: '',
+  companyAddress: '',
+  basicBankAccount: '',
+  bankAccount: '',
+  bankCode: '',
+  contactPerson: '',
+  contactPhone: '',
+  maintainer: '',
+  maintenanceTime: '',
+});
+const negotiationRecords = ref([]);
+
+// 闄勪欢鐩稿叧
+const attachmentDialogVisible = ref(false);
+const currentAttachmentList = ref([]);
+const currentFollowRecord = ref({});
+
+// 鍒楄〃鐩稿叧
+const tableData = ref([]);
+const selectedRows = ref([]);
+const userList = ref([]);
+const tableLoading = ref(false);
+const loadStatus = ref('loadmore');
+const page = reactive({
+  current: 1,
+  size: 10,
+  total: 0,
+});
+
+// 鐢ㄦ埛淇℃伅琛ㄥ崟寮规鏁版嵁
+const operationType = ref('');
+const dialogFormVisible = ref(false);
+const formRef = ref();
+const formYYs = ref({
+  contactList: [
+    {
+      contactPerson: '',
+      contactPhone: '',
+    },
+  ],
+});
+const data = reactive({
+  searchForm: {
+    customerName: '',
+    customerType: '',
+  },
+  form: {
+    customerName: '',
+    taxpayerIdentificationNumber: '',
+    companyAddress: '',
+    companyPhone: '',
+    contactPerson: '',
+    contactPhone: '',
+    maintainer: '',
+    maintenanceTime: '',
+    basicBankAccount: '',
+    bankAccount: '',
+    bankCode: '',
+    customerType: '',
+  },
+  rules: {
+    customerName: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }],
+    taxpayerIdentificationNumber: [
+      { required: true, message: '璇疯緭鍏�', trigger: 'blur' },
+    ],
+    companyAddress: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }],
+    companyPhone: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }],
+    basicBankAccount: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }],
+    bankAccount: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }],
+    bankCode: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' }],
+    customerType: [{ required: true, message: '璇烽�夋嫨', trigger: 'change' }],
+  },
+});
+const upload = reactive({
+  open: false,
+  title: '',
+  isUploading: false,
+  headers: { Authorization: 'Bearer ' + getToken() },
+  url: import.meta.env.VITE_APP_BASE_API + '/basic/customer/importData',
+  fileName: '',
+  filePath: '',
+});
+
+const { searchForm, form, rules } = toRefs(data);
+
+// 閫夋嫨瀹㈡埛鍒嗙被锛堟悳绱級
+const onTypeConfirm = (e) => {
+  searchForm.value.customerType = e.value[0];
+  showTypePicker.value = false;
+  handleQuery();
+};
+
+// 閫夋嫨瀹㈡埛鍒嗙被锛堣〃鍗曪級
+const onFormTypeConfirm = (e) => {
+  form.value.customerType = e.value[0];
+  showFormTypePicker.value = false;
+};
+
+// 閫夋嫨鏃ユ湡
+const onDateConfirm = (e) => {
+  const date = new Date(e.value);
+  form.value.maintenanceTime = formatDate(date);
+  showDatePicker.value = false;
+};
+
+// 閫夋嫨鎻愰啋鏃堕棿
+const onReminderTimeConfirm = (e) => {
+  const date = new Date(e.value);
+  reminderForm.reminderTime = formatDateTime(date);
+  showReminderTimePicker.value = false;
+};
+
+// 閫夋嫨璺熻繘鏂瑰紡
+const onFollowMethodConfirm = (e) => {
+  negotiationForm.followUpMethod = e.value[0];
+  showFollowMethodPicker.value = false;
+};
+
+// 閫夋嫨璺熻繘绋嬪害
+const onFollowLevelConfirm = (e) => {
+  negotiationForm.followUpLevel = e.value[0];
+  showFollowLevelPicker.value = false;
+};
+
+// 閫夋嫨璺熻繘鏃堕棿
+const onFollowTimeConfirm = (e) => {
+  const date = new Date(e.value);
+  negotiationForm.followUpTime = formatDateTime(date);
+  showFollowTimePicker.value = false;
+};
+
+// 鏍煎紡鍖栨棩鏈�
+const formatDate = (date) => {
+  const year = date.getFullYear();
+  const month = String(date.getMonth() + 1).padStart(2, '0');
+  const day = String(date.getDate()).padStart(2, '0');
+  return `${year}-${month}-${day}`;
+};
+
+// 鏍煎紡鍖栨棩鏈熸椂闂�
+const formatDateTime = (date) => {
+  const year = date.getFullYear();
+  const month = String(date.getMonth() + 1).padStart(2, '0');
+  const day = String(date.getDate()).padStart(2, '0');
+  const hours = String(date.getHours()).padStart(2, '0');
+  const minutes = String(date.getMinutes()).padStart(2, '0');
+  const seconds = String(date.getSeconds()).padStart(2, '0');
+  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+};
+
+// 閲嶇疆鎼滅储
+const resetSearch = () => {
+  searchForm.value.customerName = '';
+  searchForm.value.customerType = '';
+  handleQuery();
+};
+
+// 鏂板鑱旂郴浜�
+const addNewContact = () => {
+  formYYs.value.contactList.push({
+    contactPerson: '',
+    contactPhone: '',
+  });
+};
+
+// 鍒犻櫎鑱旂郴浜�
+const removeContact = (index) => {
+  if (formYYs.value.contactList.length > 1) {
+    formYYs.value.contactList.splice(index, 1);
+  } else {
+    uni.showToast({ title: '鑷冲皯淇濈暀涓�涓仈绯讳汉', icon: 'none' });
+  }
+};
+
+// 鏌ヨ鍒楄〃
+const handleQuery = () => {
+  page.current = 1;
+  getList();
+};
+
+// 鍔犺浇鏇村
+const loadMore = () => {
+  if (loadStatus.value === 'nomore') return;
+  page.current++;
+  getList(true);
+};
+
+const getList = (isLoadMore = false) => {
+  tableLoading.value = true;
+  loadStatus.value = 'loading';
+  listCustomer({ ...searchForm.value, ...page })
+    .then((res) => {
+      tableLoading.value = false;
+      if (isLoadMore) {
+        tableData.value = [...tableData.value, ...res.records];
+      } else {
+        tableData.value = res.records;
+      }
+      page.total = res.total;
+      loadStatus.value = tableData.value.length >= res.total ? 'nomore' : 'loadmore';
+    })
+    .catch(() => {
+      tableLoading.value = false;
+      loadStatus.value = 'loadmore';
+    });
+};
+
+// 鎻愪氦涓婁紶鏂囦欢
+const chooseFile = () => {
+  uni.chooseFile({
+    count: 1,
+    extension: ['.xls', '.xlsx'],
+    success: (res) => {
+      const tempFile = res.tempFiles[0];
+      upload.fileName = tempFile.name;
+      upload.filePath = tempFile.path;
+    },
+  });
+};
+
+const submitFileForm = () => {
+  if (!upload.filePath) {
+    uni.showToast({ title: '璇烽�夋嫨鏂囦欢', icon: 'none' });
+    return;
+  }
+  upload.isUploading = true;
+  uni.uploadFile({
+    url: upload.url,
+    filePath: upload.filePath,
+    name: 'file',
+    header: upload.headers,
+    success: (res) => {
+      upload.isUploading = false;
+      const data = JSON.parse(res.data);
+      if (data.code === 200) {
+        uni.showToast({ title: '涓婁紶鎴愬姛', icon: 'success' });
+        upload.open = false;
+        getList();
+      } else {
+        uni.showToast({ title: data.msg || '涓婁紶澶辫触', icon: 'none' });
+      }
+    },
+    fail: () => {
+      upload.isUploading = false;
+      uni.showToast({ title: '涓婁紶澶辫触', icon: 'none' });
+    },
+  });
+};
+
+// 瀵煎叆鎸夐挳鎿嶄綔
+const handleImport = () => {
+  upload.title = '瀹㈡埛瀵煎叆';
+  upload.open = true;
+  upload.fileName = '';
+  upload.filePath = '';
+};
+
+// 涓嬭浇妯℃澘
+const importTemplate = () => {
+  proxy.download('/basic/customer/downloadTemplate', {}, '瀹㈡埛瀵煎叆妯℃澘.xlsx');
+};
+
+// 鎵撳紑寮规
+const openForm = (type, row) => {
+  operationType.value = type;
+  form.value = {
+    customerName: '',
+    taxpayerIdentificationNumber: '',
+    companyAddress: '',
+    companyPhone: '',
+    contactPerson: '',
+    contactPhone: '',
+    maintainer: userStore.nickName,
+    maintenanceTime: getCurrentDate(),
+    basicBankAccount: '',
+    bankAccount: '',
+    bankCode: '',
+    customerType: '',
+  };
+  formYYs.value.contactList = [
+    {
+      contactPerson: '',
+      contactPhone: '',
+    },
+  ];
+  userListNoPage().then((res) => {
+    userList.value = res.data;
+  });
+  if (type === 'edit' && row) {
+    getCustomer(row.id).then((res) => {
+      form.value = { ...res.data };
+      formYYs.value.contactList = res.data.contactPerson
+        ? res.data.contactPerson.split(',').map((item, index) => {
+            return {
+              contactPerson: item,
+              contactPhone: res.data.contactPhone
+                ? res.data.contactPhone.split(',')[index] || ''
+                : '',
+            };
+          })
+        : [{ contactPerson: '', contactPhone: '' }];
+    });
+  }
+  dialogFormVisible.value = true;
+};
+
+// 鎻愪氦琛ㄥ崟
+const submitForm = () => {
+  formRef.value.validate().then(() => {
+    if (operationType.value === 'edit') {
+      submitEdit();
+    } else {
+      submitAdd();
+    }
+  }).catch(() => {});
+};
+
+// 鎻愪氦鏂板
+const submitAdd = () => {
+  if (formYYs.value.contactList.length < 1) {
+    uni.showToast({ title: '璇疯嚦灏戞坊鍔犱竴涓仈绯讳汉', icon: 'none' });
+    return;
+  }
+  form.value.contactPerson = formYYs.value.contactList
+    .map((item) => item.contactPerson)
+    .join(',');
+  form.value.contactPhone = formYYs.value.contactList
+    .map((item) => item.contactPhone)
+    .join(',');
+  addCustomer(form.value).then((res) => {
+    uni.showToast({ title: '鎻愪氦鎴愬姛', icon: 'success' });
+    closeDia();
+    getList();
+  });
+};
+
+// 鎻愪氦淇敼
+const submitEdit = () => {
+  form.value.contactPerson = formYYs.value.contactList
+    .map((item) => item.contactPerson)
+    .join(',');
+  form.value.contactPhone = formYYs.value.contactList
+    .map((item) => item.contactPhone)
+    .join(',');
+  updateCustomer(form.value).then((res) => {
+    uni.showToast({ title: '鎻愪氦鎴愬姛', icon: 'success' });
+    closeDia();
+    getList();
+  });
+};
+
+// 鍏抽棴寮规
+const closeDia = () => {
+  formRef.value?.resetFields?.();
+  dialogFormVisible.value = false;
+};
+
+// 瀵煎嚭
+const handleOut = () => {
+  uni.showModal({
+    title: '瀵煎嚭',
+    content: '閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�',
+    success: (res) => {
+      if (res.confirm) {
+        proxy.download('/basic/customer/export', {}, '瀹㈡埛妗f.xlsx');
+      }
+    },
+  });
+};
+
+// 鍒犻櫎
+const handleDelete = () => {
+  if (selectedRows.value.length === 0) {
+    uni.showToast({ title: '璇烽�夋嫨鏁版嵁', icon: 'none' });
+    return;
+  }
+  const unauthorizedData = selectedRows.value.filter(
+    (item) => item.maintainer !== userStore.nickName
+  );
+  if (unauthorizedData.length > 0) {
+    uni.showToast({ title: '涓嶅彲鍒犻櫎浠栦汉缁存姢鐨勬暟鎹�', icon: 'none' });
+    return;
+  }
+  const ids = selectedRows.value.map((item) => item.id);
+  uni.showModal({
+    title: '鍒犻櫎鎻愮ず',
+    content: '閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�',
+    success: (res) => {
+      if (res.confirm) {
+        tableLoading.value = true;
+        delCustomer(ids)
+          .then(() => {
+            uni.showToast({ title: '鍒犻櫎鎴愬姛', icon: 'success' });
+            getList();
+          })
+          .finally(() => {
+            tableLoading.value = false;
+          });
+      }
+    },
+  });
+};
+
+// 鎵撳紑鍥炶鎻愰啋寮圭獥
+const openReminderDialog = (row) => {
+  currentCustomerId.value = row.id;
+  reminderForm.customerName = row.customerName;
+  reminderForm.reminderSwitch = false;
+  reminderForm.reminderContent = '';
+  reminderForm.reminderTime = '';
+  delete reminderForm.id;
+
+  getReturnVisit(row.id)
+    .then((res) => {
+      if (res.code === 200 && res.data) {
+        reminderForm.reminderSwitch = res.data.isEnabled === 1;
+        reminderForm.reminderContent = res.data.content;
+        reminderForm.reminderTime = res.data.reminderTime;
+        reminderForm.id = res.data.id;
+      }
+    })
+    .catch((error) => {
+      console.error('鑾峰彇鍥炶鎻愰啋澶辫触:', error);
+    });
+
+  reminderDialogVisible.value = true;
+};
+
+// 鍏抽棴鍥炶鎻愰啋寮圭獥
+const closeReminderDialog = () => {
+  reminderFormRef.value?.resetFields?.();
+  reminderDialogVisible.value = false;
+};
+
+// 鎻愪氦鍥炶鎻愰啋
+const submitReminderForm = () => {
+  reminderFormRef.value.validate().then(() => {
+    const submitvalue = reminderForm.id
+      ? {
+          id: reminderForm.id,
+          customerId: currentCustomerId.value,
+          isEnabled: reminderForm.reminderSwitch ? 1 : 0,
+          content: reminderForm.reminderContent,
+          reminderTime: reminderForm.reminderTime,
+          remindUserId: userStore.id,
+        }
+      : {
+          customerId: currentCustomerId.value,
+          isEnabled: reminderForm.reminderSwitch ? 1 : 0,
+          content: reminderForm.reminderContent,
+          reminderTime: reminderForm.reminderTime,
+          remindUserId: userStore.id,
+        };
+
+    addReturnVisit(submitvalue)
+      .then((res) => {
+        if (res.code === 200) {
+          uni.showToast({ title: '鍥炶鎻愰啋璁剧疆鎴愬姛', icon: 'success' });
+          closeReminderDialog();
+        } else {
+          uni.showToast({ title: res.msg || '璁剧疆澶辫触', icon: 'none' });
+        }
+      })
+      .catch((error) => {
+        console.error('璁剧疆鍥炶鎻愰啋澶辫触:', error);
+        uni.showToast({ title: '璁剧疆澶辫触', icon: 'none' });
+      });
+  }).catch(() => {});
+};
+
+// 鎵撳紑娲借皥杩涘害寮圭獥
+const openNegotiationDialog = (row) => {
+  negotiationForm.customerName = row.customerName;
+  negotiationForm.customerId = row.id;
+  negotiationForm.followUpMethod = '';
+  negotiationForm.followUpLevel = '';
+  negotiationForm.followUpTime = '';
+  negotiationForm.followerUserName = userStore.nickName;
+  negotiationForm.content = '';
+  delete negotiationForm.editIndex;
+  delete negotiationForm.id;
+  negotiationDialogVisible.value = true;
+};
+
+// 鍏抽棴娲借皥杩涘害寮圭獥
+const closeNegotiationDialog = () => {
+  negotiationFormRef.value?.resetFields?.();
+  delete negotiationForm.editIndex;
+  delete negotiationForm.id;
+  negotiationDialogVisible.value = false;
+};
+
+// 鎻愪氦娲借皥杩涘害
+const submitNegotiationForm = () => {
+  negotiationFormRef.value.validate().then(() => {
+    const isEdit = negotiationForm.editIndex !== undefined;
+
+    if (isEdit) {
+      updateCustomerFollow(negotiationForm).then((res) => {
+        getCustomer(negotiationForm.customerId).then((res) => {
+          negotiationRecords.value = res.data.followUpList || [];
+        });
+        uni.showToast({ title: '淇敼鎴愬姛', icon: 'success' });
+        closeNegotiationDialog();
+      });
+    } else {
+      addCustomerFollow(negotiationForm).then((res) => {
+        const newRecord = {
+          followUpTime: negotiationForm.followUpTime,
+          followUpMethod: negotiationForm.followUpMethod,
+          followUpLevel: negotiationForm.followUpLevel,
+          followerUserName: negotiationForm.followerUserName,
+          content: negotiationForm.content,
+        };
+        negotiationRecords.value.unshift(newRecord);
+        uni.showToast({ title: '鎻愪氦鎴愬姛', icon: 'success' });
+        closeNegotiationDialog();
+        getList();
+      });
+    }
+  }).catch(() => {});
+};
+
+// 鎵撳紑璇︽儏寮圭獥
+const openDetailDialog = (row) => {
+  getCustomer(row.id).then((res) => {
+    Object.assign(detailForm, res.data);
+    negotiationRecords.value = res.data.followUpList || [];
+    detailDialogVisible.value = true;
+  });
+};
+
+// 鍏抽棴璇︽儏寮圭獥
+const closeDetailDialog = () => {
+  detailDialogVisible.value = false;
+};
+
+// 淇敼娲借皥璁板綍
+const editNegotiationRecord = (row, index) => {
+  Object.assign(negotiationForm, {
+    customerName: row.customerName,
+    customerId: row.customerId,
+    followUpMethod: row.followUpMethod,
+    followUpLevel: row.followUpLevel,
+    followUpTime: row.followUpTime,
+    followerUserName: row.followerUserName,
+    content: row.content,
+    id: row.id,
+    editIndex: index,
+  });
+  negotiationDialogVisible.value = true;
+};
+
+// 鍒犻櫎娲借皥璁板綍
+const deleteNegotiationRecord = (row, index) => {
+  uni.showModal({
+    title: '鍒犻櫎鎻愮ず',
+    content: '纭畾瑕佸垹闄よ繖鏉℃唇璋堣褰曞悧锛�',
+    success: (res) => {
+      if (res.confirm) {
+        delCustomerFollow(row.id).then(() => {
+          getCustomer(row.customerId).then((res) => {
+            negotiationRecords.value = res.data.followUpList || [];
+          });
+          uni.showToast({ title: '鍒犻櫎鎴愬姛', icon: 'success' });
+        });
+        negotiationRecords.value.splice(index, 1);
+      }
+    },
+  });
+};
+
+// 鎵撳紑闄勪欢寮圭獥
+const openAttachmentDialog = (row) => {
+  currentFollowRecord.value = row;
+  currentAttachmentList.value = (row.fileList || []).map((file, index) => ({
+    name: file.fileName,
+    url: file.fileUrl,
+    size: file.fileSize,
+    id: file.id,
+    uid: file.id || index,
+    status: 'success',
+  }));
+  attachmentDialogVisible.value = true;
+};
+
+// 鍏抽棴闄勪欢寮圭獥
+const closeAttachmentDialog = () => {
+  attachmentDialogVisible.value = false;
+  currentFollowRecord.value = {};
+  currentAttachmentList.value = [];
+};
+
+// 閫夋嫨闄勪欢
+const chooseAttachment = () => {
+  uni.chooseFile({
+    count: 1,
+    success: (res) => {
+      const tempFile = res.tempFiles[0];
+      uploadAttachment(tempFile);
+    },
+  });
+};
+
+// 涓婁紶闄勪欢
+const uploadAttachment = (file) => {
+  const maxSize = 50 * 1024 * 1024;
+  if (file.size > maxSize) {
+    uni.showToast({ title: '鏂囦欢澶у皬涓嶈兘瓒呰繃50MB', icon: 'none' });
+    return;
+  }
+  const uploadUrl = currentFollowRecord.value.id
+    ? import.meta.env.VITE_APP_BASE_API +
+      '/basic/customer-follow/upload/' +
+      currentFollowRecord.value.id
+    : import.meta.env.VITE_APP_BASE_API + '/basic/customer-follow/upload';
+
+  uni.uploadFile({
+    url: uploadUrl,
+    filePath: file.path,
+    name: 'file',
+    header: { Authorization: 'Bearer ' + getToken() },
+    success: (res) => {
+      const data = JSON.parse(res.data);
+      if (data.code === 200) {
+        uni.showToast({ title: '涓婁紶鎴愬姛', icon: 'success' });
+        currentAttachmentList.value.push({
+          name: file.name,
+          size: file.size,
+          url: data.data?.url || '',
+          id: data.data?.id,
+          uid: Date.now(),
+          status: 'success',
+        });
+      } else {
+        uni.showToast({ title: data.msg || '涓婁紶澶辫触', icon: 'none' });
+      }
+    },
+    fail: () => {
+      uni.showToast({ title: '涓婁紶澶辫触', icon: 'none' });
+    },
+  });
+};
+
+// 鏍煎紡鍖栨枃浠跺ぇ灏�
+const formatFileSize = (size) => {
+  if (!size) return '0 B';
+  if (size < 1024) {
+    return size + ' B';
+  } else if (size < 1024 * 1024) {
+    return (size / 1024).toFixed(2) + ' KB';
+  } else {
+    return (size / (1024 * 1024)).toFixed(2) + ' MB';
+  }
+};
+
+// 涓嬭浇闄勪欢
+const downloadAttachment = (row) => {
+  if (row.url) {
+    proxy.$download.name(row.url);
+  } else {
+    uni.showToast({ title: '涓嬭浇閾炬帴涓嶅瓨鍦�', icon: 'none' });
+  }
+};
+
+// 鍒犻櫎闄勪欢
+const deleteAttachment = (row, index) => {
+  uni.showModal({
+    title: '鍒犻櫎鎻愮ず',
+    content: '纭畾瑕佸垹闄よ繖涓檮浠跺悧锛�',
+    success: (res) => {
+      if (res.confirm) {
+        const deleteUrl =
+          import.meta.env.VITE_APP_BASE_API +
+          '/basic/customer-follow/file/' +
+          row.id;
+        uni.request({
+          url: deleteUrl,
+          method: 'DELETE',
+          header: {
+            Authorization: 'Bearer ' + getToken(),
+            'Content-Type': 'application/json',
+          },
+          success: (res) => {
+            if (res.data.code === 200) {
+              currentAttachmentList.value.splice(index, 1);
+              uni.showToast({ title: '鍒犻櫎鎴愬姛', icon: 'success' });
+            } else {
+              uni.showToast({ title: res.data.msg || '鍒犻櫎澶辫触', icon: 'none' });
+            }
+          },
+          fail: () => {
+            uni.showToast({ title: '鍒犻櫎澶辫触', icon: 'none' });
+          },
+        });
+      }
+    },
+  });
+};
+
+// 鑾峰彇褰撳墠鏃ユ湡
+function getCurrentDate() {
+  const today = new Date();
+  const year = today.getFullYear();
+  const month = String(today.getMonth() + 1).padStart(2, '0');
+  const day = String(today.getDate()).padStart(2, '0');
+  return `${year}-${month}-${day}`;
+}
+
+onMounted(() => {
+  getList();
+});
+</script>
+
+<style scoped lang="scss">
+.app-container {
+  min-height: 100vh;
+  background-color: #f5f5f5;
+  padding: 20rpx;
+}
+
+.search-section {
+  background-color: #fff;
+  padding: 20rpx;
+  border-radius: 12rpx;
+  margin-bottom: 20rpx;
+}
+
+.search-row {
+  margin-bottom: 16rpx;
+}
+
+.search-actions {
+  display: flex;
+  gap: 20rpx;
+  justify-content: flex-end;
+}
+
+.action-bar {
+  display: flex;
+  gap: 16rpx;
+  margin-bottom: 20rpx;
+  flex-wrap: wrap;
+}
+
+.customer-list {
+  height: calc(100vh - 300rpx);
+}
+
+.customer-card {
+  background-color: #fff;
+  border-radius: 12rpx;
+  padding: 24rpx;
+  margin-bottom: 20rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20rpx;
+  padding-bottom: 16rpx;
+  border-bottom: 1rpx solid #f0f0f0;
+}
+
+.customer-name {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+.card-body {
+  margin-bottom: 20rpx;
+}
+
+.info-row {
+  display: flex;
+  margin-bottom: 12rpx;
+}
+
+.label {
+  color: #666;
+  font-size: 26rpx;
+  width: 140rpx;
+  flex-shrink: 0;
+}
+
+.value {
+  color: #333;
+  font-size: 26rpx;
+  flex: 1;
+}
+
+.card-footer {
+  display: flex;
+  gap: 16rpx;
+  justify-content: flex-end;
+  padding-top: 16rpx;
+  border-top: 1rpx solid #f0f0f0;
+}
+
+.popup-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 30rpx;
+  border-bottom: 1rpx solid #eee;
+}
+
+.popup-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+.popup-body {
+  padding: 30rpx;
+  max-height: 60vh;
+}
+
+.popup-footer {
+  display: flex;
+  gap: 20rpx;
+  padding: 20rpx 30rpx;
+  border-top: 1rpx solid #eee;
+}
+
+.contact-section {
+  background-color: #f9f9f9;
+  padding: 20rpx;
+  border-radius: 8rpx;
+  margin-bottom: 20rpx;
+}
+
+.contact-phone-row {
+  display: flex;
+  gap: 16rpx;
+  align-items: center;
+}
+
+.upload-area {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 60rpx;
+  border: 2rpx dashed #dcdcdc;
+  border-radius: 8rpx;
+  margin: 30rpx 0;
+}
+
+.upload-text {
+  font-size: 28rpx;
+  color: #666;
+  margin-top: 16rpx;
+}
+
+.upload-tip {
+  font-size: 24rpx;
+  color: #999;
+  margin-top: 8rpx;
+}
+
+.file-name {
+  font-size: 26rpx;
+  color: #333;
+  margin-bottom: 20rpx;
+  word-break: break-all;
+}
+
+.detail-section {
+  margin-bottom: 30rpx;
+}
+
+.section-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20rpx;
+}
+
+.section-title {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+.info-list {
+  background-color: #f9f9f9;
+  padding: 20rpx;
+  border-radius: 8rpx;
+}
+
+.info-item {
+  display: flex;
+  padding: 12rpx 0;
+  border-bottom: 1rpx solid #eee;
+}
+
+.info-item:last-child {
+  border-bottom: none;
+}
+
+.info-label {
+  color: #666;
+  font-size: 26rpx;
+  width: 200rpx;
+  flex-shrink: 0;
+}
+
+.info-value {
+  color: #333;
+  font-size: 26rpx;
+  flex: 1;
+  word-break: break-all;
+}
+
+.record-card {
+  background-color: #f9f9f9;
+  padding: 20rpx;
+  border-radius: 8rpx;
+  margin-bottom: 16rpx;
+}
+
+.record-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 12rpx;
+}
+
+.record-time {
+  font-size: 26rpx;
+  color: #666;
+}
+
+.record-body {
+  margin-bottom: 12rpx;
+}
+
+.record-level {
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 8rpx;
+  display: block;
+}
+
+.record-content {
+  font-size: 26rpx;
+  color: #666;
+  line-height: 1.5;
+}
+
+.record-footer {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding-top: 12rpx;
+  border-top: 1rpx solid #eee;
+}
+
+.record-user {
+  font-size: 24rpx;
+  color: #999;
+}
+
+.record-actions {
+  display: flex;
+  gap: 12rpx;
+}
+
+.file-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 20rpx;
+  background-color: #f9f9f9;
+  border-radius: 8rpx;
+  margin-bottom: 16rpx;
+}
+
+.file-info {
+  display: flex;
+  align-items: center;
+  flex: 1;
+  overflow: hidden;
+}
+
+.file-detail {
+  margin-left: 16rpx;
+  flex: 1;
+  overflow: hidden;
+}
+
+.file-name {
+  font-size: 26rpx;
+  color: #333;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.file-size {
+  font-size: 24rpx;
+  color: #999;
+  margin-top: 4rpx;
+}
+
+.file-actions {
+  display: flex;
+  gap: 12rpx;
+}
+
+.empty-text {
+  text-align: center;
+  padding: 40rpx;
+  color: #999;
+  font-size: 28rpx;
+}
+</style>

--
Gitblit v1.9.3