已添加1个文件
已修改5个文件
1621 ■■■■■ 文件已修改
src/api/basicData/customerFile.js 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/config.js 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/manifest.json 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/upkeep/maintain.vue 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/customerFile/index.vue 1544 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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'
    })
}
src/config.js
@@ -1,11 +1,6 @@
// åº”用全局配置
const config = {
  //  baseUrl: 'https://vue.ruoyi.vip/prod-api',
  // baseUrl: 'http://localhost/prod-api',
  baseUrl: 'https://1181ybjh99334.vicp.fun', // é‡‘鹰黄金
  // baseUrl: 'http://192.168.1.147:9036',
   //cloud后台网关地址
  //  baseUrl: 'http://192.168.10.3:8080',
  baseUrl: 'http://114.132.189.42:9048', // é—½å…´çŸ³æ
   // åº”用信息
   appInfo: {
     // åº”用名称
src/manifest.json
@@ -1,6 +1,6 @@
{
    "name" : "金鹰黄金",
    "appid" : "__UNI__BA080C6",
    "name" : "闽兴石材",
    "appid" : "__UNI__76F6936",
    "description" : "",
    "versionName" : "1.0.0",
    "versionCode" : "100",
src/pages/equipmentManagement/upkeep/maintain.vue
@@ -27,18 +27,17 @@
                </template>
            </u-form-item>
            
            <u-form-item label="保养结果" prop="maintenanceResult" required border-bottom>
                <u-input
                    v-model="maintenanceResultText"
                    placeholder="请选择保养结果"
                    readonly
                    @click="showResultPicker"
                    clearable
                />
                <template #right>
                    <u-icon name="arrow-right" @click.stop="showResultPicker" />
                </template>
            </u-form-item>
            <u-form-item label="保养结果" prop="maintenanceResult" required border-bottom @click="showResultPicker">
            <u-input
                v-model="maintenanceResultText"
                placeholder="请选择保养结果"
                readonly
                clearable
            />
            <template #right>
                <u-icon name="arrow-right" />
            </template>
        </u-form-item>
            
            <!-- æäº¤æŒ‰é’® -->
            <view class="footer-btns">
src/pages/index.vue
@@ -11,7 +11,7 @@
        <view class="hero-section">
            <view class="bg-img">
                <view class="hero-content">
                    <text class="hero-title">金鹰黄金</text>
                    <text class="hero-title">闽兴石材</text>
                </view>
                <view class="hero-wave"></view>
            </view>
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、xlsx æ ¼å¼</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', {}, '客户档案.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>