hurx пре 1 дан
родитељ
комит
a3009388e9

+ 16 - 0
src/api/pc/organization/index.ts

@@ -130,6 +130,22 @@ export function deleteContact(ids: number[]) {
   });
 }
 
+/**
+ * 修改主联系人
+ * @param id
+ */
+export function changeIsPrimary(id: string | number, isPrimary: string) {
+  const data = {
+    id,
+    isPrimary
+  };
+  return request({
+    url: '/customer/organization/contact/changeIsPrimary',
+    method: 'put',
+    data: data
+  });
+}
+
 // ==================== 角色管理 ====================
 
 /**

+ 51 - 0
src/api/pc/valueAdded/index.ts

@@ -42,3 +42,54 @@ export function deleteComplaintsSuggestions(ids: number[]) {
     method: 'delete'
   });
 }
+
+/**
+ * 查询维保列表
+ */
+export function getMaintainInfoList(params?: any) {
+  return request({
+    url: '/customer/pcMaintainInfo/list',
+    method: 'get',
+    params: params
+  });
+}
+
+/**
+ * 查询维保详情
+ */
+export function getMaintainInfo(id: number) {
+  return request({
+    url: `/customer/pcMaintainInfo/${id}`,
+    method: 'get'
+  });
+}
+
+/**
+ * 新增维保
+ */
+export function addMaintainInfo(data: any) {
+  return request({
+    url: '/customer/pcMaintainInfo',
+    method: 'post',
+    data: data
+  });
+}
+
+/**
+ * 修改维保
+ */
+export function updateMaintainInfo(data: any) {
+  return request({
+    url: '/customer/pcMaintainInfo',
+    method: 'put',
+    data: data
+  });
+}
+
+export const listServerItem = (query) => {
+  return request({
+    url: '/customer/serverItem/list',
+    method: 'get',
+    params: query
+  });
+};

+ 45 - 15
src/views/enterprise/changePerson/index.vue

@@ -43,7 +43,7 @@
         <el-form ref="step1FormRef" :model="step1Form" label-width="120px" class="verify-form">
           <el-form-item label="手机号码:">
             <div class="form-item-wrapper">
-              <el-input v-model="step1Form.phone" placeholder="请输入手机号码" class="form-input" />
+              <el-input v-model="step1Form.phone" placeholder="请输入手机号码" class="form-input" disabled />
               <span class="form-tip">若该手机号已无法使用请联系客服</span>
             </div>
           </el-form-item>
@@ -60,11 +60,11 @@
             </div>
           </el-form-item>
 
-          <el-form-item label=" ">
+          <!-- <el-form-item label=" ">
             <div class="captcha-box">
               <el-radio v-model="step1Form.verified" :label="true" class="captcha-radio">点击验证</el-radio>
             </div>
-          </el-form-item>
+          </el-form-item> -->
 
           <el-form-item label=" " class="action-item">
             <el-button type="primary" class="next-btn" @click="handleNextStep">下一步</el-button>
@@ -76,14 +76,13 @@
       <div class="step-content" v-if="currentStep === 2">
         <el-form label-width="120px" class="verify-form">
           <el-form-item label="选择新负责人:">
-            <el-select v-model="step2Form.person" placeholder="请选择新采购负责人" class="form-input">
-              <el-option label="张三" value="zhangsan" />
-              <el-option label="李四" value="lisi" />
+            <el-select v-model="step2Form.personId" placeholder="请选择新采购负责人" class="form-input">
+              <el-option v-for="item in contactList" :key="item.id" :label="`${item.contactName} , ${item.phone}`" :value="item.id" />
             </el-select>
           </el-form-item>
           <el-form-item label=" " class="action-item">
             <el-button @click="currentStep = 1">上一步</el-button>
-            <el-button type="primary" class="next-btn" @click="currentStep = 3" style="width: auto; padding: 0 40px">提交</el-button>
+            <el-button type="primary" class="next-btn" @click="changeContactBtn" style="width: auto; padding: 0 40px">提交</el-button>
           </el-form-item>
         </el-form>
       </div>
@@ -103,23 +102,32 @@
 <script setup lang="ts">
 import { ref, reactive } from 'vue';
 import { Check, CircleCheckFilled } from '@element-plus/icons-vue';
+import { getCurrentUserInfo } from '@/api/pc/organization/index';
 import { ElMessage } from 'element-plus';
 import { smsCode } from '@/api/breg/index';
-
+import { getContactList, changeIsPrimary } from '@/api/pc/organization/index';
+import { useRoute } from 'vue-router';
 const currentStep = ref(1);
+const route = useRoute();
 
 const step1Form = reactive({
-  phone: '',
+  phone: route.query.phone as any,
   code: '',
   verified: false
 });
 const timer = ref<any>(null);
 const step2Form = reactive({
-  person: ''
+  personId: ''
 });
+const contactList = ref([]);
 
 const countdown = ref(0);
 
+const getCurrentUser = async () => {
+  const res = await getCurrentUserInfo();
+  step1Form.phone = res.data.phone;
+};
+
 // 发送验证码
 const handleSendCode = () => {
   if (countdown.value > 0) return;
@@ -150,17 +158,39 @@ const startCountdown = () => {
     countdown.value--;
   }, 1000);
 };
+
+const loadContactList = async () => {
+  try {
+    const res = await getContactList();
+    contactList.value = res.rows || [];
+  } catch (e) {
+    ElMessage.error('加载联系人列表失败');
+  } finally {
+  }
+};
+
+const changeContactBtn = async () => {
+  try {
+    await changeIsPrimary(step2Form.personId, '0');
+    ElMessage.success('操作成功');
+    currentStep.value = 1;
+  } catch (err) {}
+};
 const handleNextStep = () => {
   if (!step1Form.phone || !step1Form.code) {
     ElMessage.warning('请填写完整的手机号和验证码');
     return;
   }
-  if (!step1Form.verified) {
-    ElMessage.warning('请完成点击验证');
-    return;
-  }
+  // if (!step1Form.verified) {
+  //   ElMessage.warning('请完成点击验证');
+  //   return;
+  // }
   currentStep.value = 2;
 };
+onMounted(() => {
+  loadContactList();
+  getCurrentUser();
+});
 </script>
 
 <style scoped lang="scss">
@@ -303,7 +333,7 @@ const handleNextStep = () => {
     font-weight: 500;
 
     &:hover {
-      color: #159c81;
+      color: #e60012;
     }
     &.is-disabled {
       color: #999;

+ 17 - 15
src/views/enterprise/companyInfo/edit.vue

@@ -93,24 +93,24 @@
           </el-row>
           <el-row :gutter="20">
             <el-col :span="12">
-              <el-form-item label="联系方式" prop="phone">
-                <el-input v-model="form.phone" placeholder="请输入" />
+              <el-form-item label="联系方式" prop="landline">
+                <el-input v-model="form.landline" placeholder="请输入" />
               </el-form-item>
             </el-col>
             <el-col :span="12">
-              <el-form-item label="邮箱" prop="email">
-                <el-input v-model="form.email" placeholder="请输入" />
+              <el-form-item label="邮箱" prop="fax">
+                <el-input v-model="form.fax" placeholder="请输入" />
               </el-form-item>
             </el-col>
           </el-row>
           <el-row :gutter="20">
             <el-col :span="24">
-              <el-form-item label="网址" prop="website">
-                <el-input v-model="form.website" placeholder="请输入" maxlength="50" show-word-limit />
+              <el-form-item label="网址" prop="url">
+                <el-input v-model="form.url" placeholder="请输入" maxlength="50" show-word-limit />
               </el-form-item>
             </el-col>
           </el-row>
-          <el-row :gutter="20">
+          <!-- <el-row :gutter="20">
             <el-col :span="12">
               <el-form-item label="营业执照" prop="businessLicense">
                 <el-upload
@@ -135,6 +135,8 @@
                 <el-upload
                   class="upload-box"
                   :action="action"
+                  :multiple="true"
+                  :limit="2"
                   :show-file-list="false"
                   :on-success="handleLegalPersonCardUrlSuccess"
                   :before-upload="beforeAvatarUpload"
@@ -149,7 +151,7 @@
                 </el-upload>
               </el-form-item>
             </el-col>
-          </el-row>
+          </el-row> -->
         </div>
       </el-form>
 
@@ -189,9 +191,9 @@ const form = reactive({
   address: '',
   industryCategoryId: '',
   enterpriseScaleId: '',
-  phone: '',
-  email: '',
-  website: '',
+  landline: '',
+  fax: '',
+  url: '',
   businessLicense: '',
   legalPersonCardUrl: ''
 });
@@ -279,7 +281,7 @@ const loadEnterpriseInfo = async () => {
       const businessInfo = data.customerBusinessInfoVo || {};
 
       form.companyName = data.customerName || data.businessCustomerName || '';
-      form.creditCode = businessInfo.socialCreditCode || '';
+      form.creditCode = data.socialCreditCode || '';
       form.legalPerson = businessInfo.legalPersonName || '';
       form.registeredCapital = businessInfo.registeredCapital || '';
       form.detailAddress = businessInfo.businessAddress || data.address || '';
@@ -289,9 +291,9 @@ const loadEnterpriseInfo = async () => {
       form.industryCategoryId = data.industryCategoryId || '';
       form.enterpriseScaleId = data.enterpriseScaleId || '';
       form.regionCodes = [data.regProvincialNo, data.regCityNo, data.regCountyNo];
-      form.phone = data.landline || '';
-      form.email = businessInfo.email || data.fax || '';
-      form.website = data.url || '';
+      form.landline = data.landline || '';
+      form.fax = data.fax || '';
+      form.url = data.url || '';
       form.businessLicense = businessInfo.businessLicense || '';
       form.legalPersonCardUrl = businessInfo.legalPersonCardUrl || '';
 

+ 14 - 7
src/views/enterprise/companyInfo/index.vue

@@ -101,7 +101,10 @@
     <div class="service-section">
       <div class="section-title"><i class="title-bar"></i>专属服务人员</div>
       <div class="service-list">
-        <div v-for="(person, index) in servicePersons" :key="index" class="service-card">
+        <span> 销售人员:{{ companyData.salesPerson }}</span
+        ><br />
+        <span> 客服支持:{{ companyData.serviceStaff }}</span>
+        <!-- <div v-for="(person, index) in servicePersons" :key="index" class="service-card">
           <el-avatar :size="50" :src="person.avatar">
             <el-icon :size="30"><User /></el-icon>
           </el-avatar>
@@ -112,9 +115,9 @@
             </div>
             <div class="person-contact">{{ person.department }} {{ person.phone }}</div>
           </div>
-        </div>
-        <div class="flex-row-center w-[100%] empty-bos" v-if="servicePersons.length === 0">
-          <el-empty image-size="100" description="暂无" />
+        </div>-->
+        <div class="flex-row-center w-[100%] empty-bos" v-if="!companyData.salesPerson">
+          <el-empty :image-size="100" description="暂无" />
         </div>
       </div>
     </div>
@@ -167,7 +170,9 @@ const companyData = reactive({
   scale: '-',
   phone: '-',
   email: '-',
-  address: '-'
+  address: '-',
+  salesPerson: '-',
+  serviceStaff: '-'
 });
 
 // 加载企业信息
@@ -184,15 +189,17 @@ const loadEnterpriseInfo = async () => {
 
       companyData.companyName = formatValue(data.customerName || data.businessCustomerName);
       companyData.companyCode = formatValue(data.customerNo);
-      companyData.creditCode = formatValue(businessInfo.socialCreditCode);
+      companyData.creditCode = formatValue(data.socialCreditCode);
       companyData.industry = formatValue(data.industryCategory);
       companyData.scale = formatValue(data.enterpriseScale);
       companyData.phone = formatValue(data.landline);
-      companyData.email = formatValue(businessInfo.email || data.fax);
+      companyData.email = formatValue(data.fax);
       companyData.address = formatValue(data.address);
       companyData.availableAmount = (parseInt(salesInfo.remainingQuota) || 0).toFixed(2); // 剩余额度
       // companyData.deptCredit = data.deptCredit || '0.00';部门额度
       companyData.creditAmount = salesInfo.creditAmount || '0.00';
+      companyData.salesPerson = formatValue(data.salesPersonName);
+      companyData.serviceStaff = formatValue(data.serviceStaffName);
     }
   } catch (error) {
     console.error('加载企业信息失败:', error);

+ 5 - 5
src/views/enterprise/purchasePlan/index.vue

@@ -45,16 +45,16 @@ import { onPath } from '@/utils/siteConfig';
 
 // 采购方案类型映射
 const typeMap: Record<string, string> = {
-  purchase: '1',      // 采购方案
-  exclusive: '2',     // 专属采购方案
-  collection: '3'     // 收藏的采购方案
+  purchase: '1', // 采购方案
+  exclusive: '2' // 专属采购方案
+  // collection: '3' // 收藏的采购方案
 };
 
 const activeTab = ref('purchase');
 const tabs = [
   { key: 'purchase', label: '采购方案' },
-  { key: 'exclusive', label: '专属采购方案' },
-  { key: 'collection', label: '收藏的采购方案' }
+  { key: 'exclusive', label: '专属采购方案' }
+  // { key: 'collection', label: '收藏的采购方案' }
 ];
 const queryParams = reactive({ pageNum: 1, pageSize: 10 });
 const total = ref(0);

+ 6 - 6
src/views/enterprise/securitySetting/resetPassword.vue

@@ -52,9 +52,9 @@
             <el-input v-model="step1Form.code" placeholder="短信验证码" class="form-input code-input" />
             <el-button @click="handleSendCode" :class="['code', countdown > 0 ? 'disabled' : '']"> {{ codeText }}</el-button>
           </el-form-item>
-          <el-form-item label="" class="verify-checkbox">
+          <!-- <el-form-item label="" class="verify-checkbox">
             <el-checkbox v-model="step1Form.verified">点击验证</el-checkbox>
-          </el-form-item>
+          </el-form-item> -->
         </el-form>
         <div class="step-actions">
           <el-button type="danger" class="next-btn" @click="handleNextStep">下一步</el-button>
@@ -186,10 +186,10 @@ const handleNextStep = async () => {
   const valid = await step1FormRef.value?.validate().catch(() => false);
   if (!valid) return;
 
-  if (!step1Form.verified) {
-    ElMessage.warning('请先点击验证');
-    return;
-  }
+  // if (!step1Form.verified) {
+  //   ElMessage.warning('请先点击验证');
+  //   return;
+  // }
 
   currentStep.value = 2;
 };

+ 15 - 3
src/views/greg/index.vue

@@ -241,7 +241,9 @@
     <div class="pay-foot" v-if="nextNum != 4">
       <div class="foot-bos">
         <el-button @click="previousStep" v-if="nextNum == 2" class="bnt1">返回上一步</el-button>
-        <el-button v-if="nextNum != 3" @click="nextStep" class="bnt2" type="primary" :loading="loading">{{ nextNum == 2 ? '提交并完成注册' : '下一步' }}</el-button>
+        <el-button v-if="nextNum != 3" @click="nextStep" class="bnt2" type="primary" :loading="loading">{{
+          nextNum == 2 ? '提交并完成注册' : '下一步'
+        }}</el-button>
       </div>
     </div>
     <el-dialog
@@ -354,7 +356,19 @@ const startCountdown = () => {
 
 // 获取验证码
 const sendSmsCode = () => {
+  // 防止倒计时期间重复点击
   if (countdown.value > 0) return;
+  const phone = form.value.purchasePhone;
+
+  // 1. 基础格式校验
+  if (!validateMobile(phone)) {
+    ElMessage({
+      message: '请输入正确的手机号码',
+      type: 'warning'
+    });
+    return;
+  }
+
   // 2. 【新增】先调用接口查询用户是否存在
   selectByPhone(form.value.purchasePhone, '1')
     .then((res: any) => {
@@ -392,8 +406,6 @@ const sendSmsCode = () => {
         type: 'warning' // 或者使用 'error'
       });
     });
-
-
 };
 
 //选择省

+ 93 - 27
src/views/order/afterSale/index.vue

@@ -117,18 +117,18 @@
 
       <div class="search-filter-bar">
         <div class="left">
-          <el-input v-model="queryParams.keyword" placeholder="搜索" style="width: 150px" clearable>
+          <!-- <el-input v-model="queryParams.keyword" placeholder="搜索" style="width: 150px" clearable>
             <template #prefix
               ><el-icon><Search /></el-icon
             ></template>
-          </el-input>
-          <span class="filter-label">处理结果</span>
+          </el-input> -->
+          <!-- <span class="filter-label">处理结果</span>
           <el-select v-model="queryParams.handleResult" placeholder="请选择" style="width: 100px" clearable>
             <el-option label="已处理" value="handled" />
             <el-option label="未处理" value="unhandled" />
-          </el-select>
+          </el-select> -->
           <span class="filter-label">反馈类型</span>
-          <el-select v-model="queryParams.feedbackType" placeholder="请选择" style="width: 100px" clearable>
+          <el-select v-model="queryParams.feedbackType" placeholder="请选择" style="width: 100px" clearable @change="handleFeedbackTypeChange">
             <el-option v-for="dict in complaints_suggestion_type" :key="dict.value" :label="dict.label" :value="dict.value" />
           </el-select>
         </div>
@@ -160,8 +160,8 @@
         <el-table-column prop="processResult" label="处理结果" min-width="100" align="center" />
         <el-table-column label="操作" min-width="120" align="center">
           <template #default="{ row }">
-            <el-button type="primary" link size="small" @click="handleEditComplaint(row)">修改</el-button>
-            <el-button type="danger" link size="small" @click="handleDeleteComplaint(row)">删除</el-button>
+            <el-button type="primary" link size="small" @click="handleViewComplaint(row)">查看</el-button>
+            <!-- <el-button type="danger" link size="small" @click="handleDeleteComplaint(row)">删除</el-button> -->
           </template>
         </el-table-column>
       </el-table>
@@ -171,20 +171,60 @@
       </div>
     </template>
 
-    <el-dialog v-model="complaintDialogVisible" :title="complaintDialogTitle" width="500px">
-      <el-form ref="complaintFormRef" :model="complaintForm" :rules="complaintRules" label-width="80px">
-        <el-form-item label="反馈类型" prop="feedbackType">
-          <el-select v-model="complaintForm.feedbackType" placeholder="请选择" style="width: 100%">
-            <el-option v-for="dict in complaints_suggestion_type" :key="dict.value" :label="dict.label" :value="dict.value" />
-          </el-select>
-        </el-form-item>
-        <el-form-item label="反馈内容" prop="feedbackContent">
-          <el-input v-model="complaintForm.feedbackContent" type="textarea" :rows="4" placeholder="请输入反馈内容" />
-        </el-form-item>
-      </el-form>
+    <el-dialog v-model="complaintDialogVisible" :title="complaintDialogTitle" width="700px">
+      <template v-if="isViewMode">
+        <el-descriptions :column="1" border>
+          <el-descriptions-item label="编号">{{ currentComplaint?.id || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="反馈类型">
+            {{ getDictLabel(complaints_suggestion_type, currentComplaint?.feedbackType) || '-' }}
+          </el-descriptions-item>
+          <el-descriptions-item label="反馈内容">{{ currentComplaint?.feedbackContent || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="反馈人">{{ currentComplaint?.createName || '-' }} {{ currentComplaint?.phone || '' }}</el-descriptions-item>
+          <el-descriptions-item label="相关凭证">
+            <template v-if="currentComplaint?.relatedPictures">
+              <div class="image-preview">
+                <el-image
+                  v-for="(img, index) in (currentComplaint.relatedPictures || '').split(',').filter(Boolean)"
+                  :key="index"
+                  :src="img"
+                  :preview-src-list="(currentComplaint.relatedPictures || '').split(',').filter(Boolean)"
+                  style="width: 80px; height: 80px; margin-right: 8px; border-radius: 4px"
+                  fit="cover"
+                />
+                <span v-if="(currentComplaint.relatedPictures || '').split(',').filter(Boolean).length === 0">-</span>
+              </div>
+            </template>
+            <span v-else>-</span>
+          </el-descriptions-item>
+          <el-descriptions-item label="处理时间">
+            {{ currentComplaint?.processTime ? parseTime(currentComplaint.processTime, '{y}-{m}-{d} {h}:{i}') : '-' }}
+          </el-descriptions-item>
+          <el-descriptions-item label="处理结果">{{ currentComplaint?.processResult || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="处理反馈">{{ currentComplaint?.processFeedback || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="处理评价">{{ currentComplaint?.processEvaluation || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="结果反馈">{{ currentComplaint?.resultFeedback || '-' }}</el-descriptions-item>
+        </el-descriptions>
+      </template>
+      <template v-else>
+        <el-form ref="complaintFormRef" :model="complaintForm" :rules="complaintRules" label-width="80px">
+          <el-form-item label="反馈类型" prop="feedbackType">
+            <el-select v-model="complaintForm.feedbackType" placeholder="请选择" style="width: 100%">
+              <el-option v-for="dict in complaints_suggestion_type" :key="dict.value" :label="dict.label" :value="dict.value" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="反馈内容" prop="feedbackContent">
+            <el-input v-model="complaintForm.feedbackContent" type="textarea" :rows="4" placeholder="请输入反馈内容" />
+          </el-form-item>
+        </el-form>
+      </template>
       <template #footer>
-        <el-button @click="complaintDialogVisible = false">取消</el-button>
-        <el-button type="danger" @click="handleSubmitComplaint">确定</el-button>
+        <template v-if="isViewMode">
+          <el-button @click="complaintDialogVisible = false">关闭</el-button>
+        </template>
+        <template v-else>
+          <el-button @click="complaintDialogVisible = false">取消</el-button>
+          <el-button type="danger" @click="handleSubmitComplaint">确定</el-button>
+        </template>
       </template>
     </el-dialog>
 
@@ -234,7 +274,7 @@
 
 <script setup lang="ts">
 import { ref, reactive, computed, onMounted } from 'vue';
-import { useRouter } from 'vue-router';
+import { useRouter, useRoute } from 'vue-router';
 import { Search, RefreshRight, Tools, ChatLineSquare, Picture } from '@element-plus/icons-vue';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import { PageTitle, SearchBar, TablePagination } from '@/components';
@@ -251,12 +291,14 @@ const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const { service_type, complaints_suggestion_type } = toRefs<any>(proxy?.useDict('service_type', 'complaints_suggestion_type'));
 
 const router = useRouter();
+const route = useRoute();
 const activeMainTab = ref('return');
 const activeStatusTab = ref('all');
 const complaintDialogVisible = ref(false);
 const complaintDialogTitle = ref('新增投诉与建议');
 const complaintFormRef = ref();
 const currentComplaint = ref<any>(null);
+const isViewMode = ref(false);
 const loading = ref(false);
 const pagination = reactive({ page: 1, pageSize: 5, total: 0 });
 const returnDialogVisible = ref(false);
@@ -381,7 +423,15 @@ const handleStatusTabChange = (key: string) => {
 
 // 页面加载时获取数据
 onMounted(() => {
-  loadAfterSaleData();
+  // 检查是否有 tab=complaint 参数,如果有则切换到投诉标签
+  const tab = route.query.tab as string;
+  if (tab === 'complaint') {
+    activeMainTab.value = 'complaint';
+    activeStatusTab.value = 'all';
+    loadComplaintData();
+  } else {
+    loadAfterSaleData();
+  }
 });
 const getStatusClass = (status: string) => {
   const map: Record<string, string> = { 0: 'warning', 1: 'success', 2: 'danger', 3: 'success', 4: 'info' };
@@ -434,17 +484,16 @@ const getItemSubtotal = (item: any) => {
 };
 const handleAddComplaint = () => {
   currentComplaint.value = null;
+  isViewMode.value = false;
   complaintDialogTitle.value = '新增投诉与建议';
   complaintForm.feedbackType = '';
   complaintForm.feedbackContent = '';
   complaintDialogVisible.value = true;
 };
-const handleEditComplaint = (row: any) => {
+const handleViewComplaint = (row: any) => {
   currentComplaint.value = row;
-  complaintDialogTitle.value = '修改投诉与建议';
-  complaintForm.id = row.id;
-  complaintForm.feedbackType = row.feedbackType;
-  complaintForm.feedbackContent = row.feedbackContent || '';
+  isViewMode.value = true;
+  complaintDialogTitle.value = '查看投诉与建议';
   complaintDialogVisible.value = true;
 };
 const handleDeleteComplaint = (row: any) => {
@@ -475,12 +524,23 @@ const handleSubmitComplaint = async () => {
 
 const loadComplaintData = async () => {
   const params: any = { pageNum: pagination.page, pageSize: pagination.pageSize };
+  if (queryParams.feedbackType) {
+    params.feedbackType = queryParams.feedbackType;
+  }
+  if (queryParams.keyword) {
+    params.keyword = queryParams.keyword;
+  }
   const res = await getComplaintsSuggestionsList(params);
   if (res.code === 200 && res.rows) {
     complaintList.value = res.rows;
     pagination.total = res.total || 0;
   }
 };
+
+const handleFeedbackTypeChange = () => {
+  pagination.page = 1;
+  loadComplaintData();
+};
 </script>
 
 <style scoped lang="scss">
@@ -806,4 +866,10 @@ const loadComplaintData = async () => {
     }
   }
 }
+
+.image-preview {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+}
 </style>

+ 6 - 2
src/views/order/orderAudit/index.vue

@@ -69,10 +69,10 @@
               </div>
               <div class="product-detail">
                 <div class="product-name">{{ item.name }}</div>
-                <div class="product-spec">{{ item.spec1 }} {{ item.spec2 }}</div>
+                <div class="product-spec">{{ item.spec1 }} x{{ item.quantity }} | {{ item.spec2 }}</div>
                 <div class="product-price">¥{{ item.price }}</div>
               </div>
-              <div class="product-quantity">x{{ item.quantity }}</div>
+              <div class="product-quantity"></div>
             </div>
             <div class="product-cell amount-cell" v-if="itemIndex === 0">
               <div class="amount-info">
@@ -595,6 +595,10 @@ const handleSubmitAudit = async () => {
         .product-quantity {
           font-size: 13px;
           color: #666;
+          width: 80px;
+          text-align: right;
+          flex-shrink: 0;
+          padding-left: 10px;
         }
       }
 

+ 100 - 23
src/views/reconciliation/billManage/index.vue

@@ -1,7 +1,44 @@
 <template>
   <div class="page-container">
     <PageTitle title="对账单管理" />
-    <SearchBar :form="searchForm" :filters="filters" placeholder="对账编号" />
+    <!-- 搜索条件区域 -->
+    <div class="search-area">
+      <el-form :model="searchForm" inline>
+        <el-form-item label="对账编号">
+          <el-input v-model="searchForm.keyword" placeholder="请输入对账编号" clearable @clear="handleSearch" />
+        </el-form-item>
+        <el-form-item label="对账日期">
+          <el-date-picker
+            v-model="searchForm.dateRange"
+            type="daterange"
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            value-format="YYYY-MM-DD"
+            @change="handleSearch"
+          />
+        </el-form-item>
+        <el-form-item label="对账状态">
+          <el-select v-model="searchForm.billStatus" placeholder="请选择对账状态" clearable @change="handleSearch">
+            <el-option v-for="item in billStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="开票状态">
+          <el-select v-model="searchForm.invoiceStatus" placeholder="请选择开票状态" clearable @change="handleSearch">
+            <el-option v-for="item in invoiceStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="支付状态">
+          <el-select v-model="searchForm.payStatus" placeholder="请选择支付状态" clearable @change="handleSearch">
+            <el-option v-for="item in payStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleSearch">搜索</el-button>
+          <el-button @click="handleReset">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
     <el-table
       v-loading="loading"
       :data="tableData"
@@ -62,10 +99,10 @@
 </template>
 
 <script setup lang="ts">
-import { reactive, ref, onMounted, watch } from 'vue';
+import { reactive, ref, onMounted, watch, computed, getCurrentInstance, toRefs } from 'vue';
 import { useRouter } from 'vue-router';
 import { ElMessage, ElMessageBox } from 'element-plus';
-import { PageTitle, SearchBar, TablePagination } from '@/components';
+import { PageTitle, TablePagination } from '@/components';
 import { getStatementList, getStatementInfo, confirmStatement, applyForInvoice } from '@/api/pc/enterprise/statement';
 import type { StatementOrder } from '@/api/pc/enterprise/statementTypes';
 
@@ -84,20 +121,10 @@ const searchForm = reactive({
   payStatus: ''
 });
 
-const billStatusOptions = ref([{ label: '全部', value: '' }]);
-const invoiceStatusOptions = ref([{ label: '全部', value: '' }]);
-const payStatusOptions = ref([{ label: '全部', value: '' }]);
-
-// 状态值到文本的映射
-const billStatusMap = ref<Record<string, string>>({});
-const invoiceStatusMap = ref<Record<string, string>>({});
-const payStatusMap = ref<Record<string, string>>({});
-
-const filters = ref([
-  { field: 'billStatus', label: '对账状态', options: billStatusOptions.value },
-  { field: 'invoiceStatus', label: '开票状态', options: invoiceStatusOptions.value },
-  { field: 'payStatus', label: '支付状态', options: payStatusOptions.value }
-]);
+// 从字典生成选项,添加"全部"选项
+const billStatusOptions = computed(() => [{ label: '全部', value: '' }, ...(statement_status.value || [])]);
+const invoiceStatusOptions = computed(() => [{ label: '全部', value: '' }, ...(invoice_issuance_status.value || [])]);
+const payStatusOptions = computed(() => [{ label: '全部', value: '' }, ...(payment_status.value || [])]);
 
 const pagination = reactive({ page: 1, pageSize: 5, total: 0 });
 const tableData = ref<any[]>([]);
@@ -112,14 +139,23 @@ const form = reactive({
 const loadStatementList = async () => {
   try {
     loading.value = true;
-    const res = await getStatementList({
+    const queryParams = {
       pageNum: pagination.page,
       pageSize: pagination.pageSize,
       statementOrderNo: searchForm.keyword,
       statementStatus: searchForm.billStatus,
       isInvoiceStatus: searchForm.invoiceStatus,
-      isPaymentStatus: searchForm.payStatus
-    });
+      isPaymentStatus: searchForm.payStatus,
+      params: {} as any
+    };
+
+    // 添加日期范围参数
+    if (searchForm.dateRange && searchForm.dateRange.length === 2) {
+      queryParams.params.beginTime = searchForm.dateRange[0];
+      queryParams.params.endTime = searchForm.dateRange[1];
+    }
+
+    const res = await getStatementList(queryParams);
 
     if (res.code === 200 && res.rows) {
       tableData.value = res.rows.map((item: StatementOrder) => ({
@@ -189,7 +225,7 @@ watch(
 
 // 监听搜索条件变化
 watch(
-  () => [searchForm.keyword, searchForm.billStatus, searchForm.invoiceStatus, searchForm.payStatus],
+  () => [searchForm.keyword, searchForm.dateRange, searchForm.billStatus, searchForm.invoiceStatus, searchForm.payStatus],
   () => {
     pagination.page = 1;
     loadStatementList();
@@ -202,8 +238,6 @@ const getDictLabel = (dictOptions: any[], value: string) => {
   return dict ? dict.label : value;
 };
 
-// 加载字典数据
-
 // 页面加载时获取数据
 onMounted(() => {
   loadStatementList();
@@ -258,9 +292,52 @@ const handleApplyInvoice = async () => {
     }
   }
 };
+
+// 搜索
+const handleSearch = () => {
+  pagination.page = 1;
+  loadStatementList();
+};
+
+// 重置
+const handleReset = () => {
+  searchForm.keyword = '';
+  searchForm.dateRange = [];
+  searchForm.billStatus = '';
+  searchForm.invoiceStatus = '';
+  searchForm.payStatus = '';
+  pagination.page = 1;
+  loadStatementList();
+};
 </script>
 
 <style scoped>
+.search-area {
+  margin-bottom: 16px;
+  padding: 16px;
+  background: #fff;
+  border: 1px solid #e4e7ed;
+  border-radius: 4px;
+}
+
+.search-area .el-form {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 16px;
+  align-items: flex-end;
+}
+
+.search-area .el-form-item {
+  margin-bottom: 0;
+  margin-right: 0;
+}
+
+.search-area .el-input,
+.search-area .el-select,
+.search-area .el-date-picker {
+  width: 200px;
+}
+
 .pagination-wrapper {
   margin-top: 16px;
   display: flex;

+ 78 - 32
src/views/reconciliation/invoiceManage/index.vue

@@ -2,7 +2,34 @@
   <div class="page-container">
     <PageTitle title="开票管理" />
 
-    <SearchBar :form="searchForm" :filters="filters" placeholder="开票编号" />
+    <!-- 搜索条件区域 -->
+    <div class="search-area">
+      <el-form :model="searchForm" inline>
+        <el-form-item label="开票编号">
+          <el-input v-model="searchForm.keyword" placeholder="请输入开票编号" clearable @clear="handleSearch" />
+        </el-form-item>
+        <el-form-item label="开票日期">
+          <el-date-picker
+            v-model="searchForm.dateRange"
+            type="daterange"
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            value-format="YYYY-MM-DD"
+            @change="handleSearch"
+          />
+        </el-form-item>
+        <el-form-item label="开票状态">
+          <el-select v-model="searchForm.invoiceStatus" placeholder="请选择开票状态" clearable @change="handleSearch">
+            <el-option v-for="item in invoiceStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleSearch">搜索</el-button>
+          <el-button @click="handleReset">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
 
     <el-table v-loading="loading" :data="tableData" border style="width: 100%">
       <el-table-column prop="statementInvoiceNo" label="开票编号" min-width="130" align="center" />
@@ -33,13 +60,14 @@
 </template>
 
 <script setup lang="ts">
-import { reactive, ref, computed, onMounted, watch } from 'vue';
+import { reactive, ref, onMounted, watch, computed, getCurrentInstance, toRefs } from 'vue';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import { useRouter } from 'vue-router';
-import { PageTitle, SearchBar, TablePagination } from '@/components';
+import { PageTitle, TablePagination } from '@/components';
 import { getStatementInvoiceList } from '@/api/pc/enterprise/statement';
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
-const { invoice_status } = toRefs<any>(proxy?.useDict('invoice_issuance_status', 'statement_status', 'invoice_status'));
+const { invoice_issuance_status, invoice_status } = toRefs<any>(proxy?.useDict('invoice_issuance_status', 'invoice_status'));
+// const { invoice_status } = toRefs<any>(proxy?.useDict('invoice_issuance_status', 'statement_status', 'invoice_status'));
 const searchForm = reactive({
   keyword: '',
   dateRange: [],
@@ -49,24 +77,32 @@ const form = reactive({
   statementOrderIds: []
 });
 
-const invoiceStatusOptions = ref([{ label: '全部', value: '' }]);
-
-const filters = ref([{ field: 'invoiceStatus', label: '开票状态', options: invoiceStatusOptions.value }]);
+// 从字典生成选项,添加"全部"选项
+const invoiceStatusOptions = computed(() => [{ label: '全部', value: '' }, ...(invoice_status.value || [])]);
 
 const pagination = reactive({ page: 1, pageSize: 5, total: 0 });
 const tableData = ref<any[]>([]);
 const loading = ref(false);
 const router = useRouter();
-// 加载对账单列表
+// 加载开票列表
 const loadStatementInvoice = async () => {
   try {
     loading.value = true;
-    const res = await getStatementInvoiceList({
+    const queryParams = {
       pageNum: pagination.page,
       pageSize: pagination.pageSize,
       statementInvoiceNo: searchForm.keyword,
-      isInvoiceStatus: searchForm.invoiceStatus
-    });
+      invoiceStatus: searchForm.invoiceStatus,
+      params: {} as any
+    };
+
+    // 添加日期范围参数
+    if (searchForm.dateRange && searchForm.dateRange.length === 2) {
+      queryParams.params.beginTime = searchForm.dateRange[0];
+      queryParams.params.endTime = searchForm.dateRange[1];
+    }
+
+    const res = await getStatementInvoiceList(queryParams);
 
     if (res.code === 200 && res.rows) {
       tableData.value = res.rows.map((item: any) => ({
@@ -104,13 +140,28 @@ watch(
 
 // 监听搜索条件变化
 watch(
-  () => [searchForm.keyword, searchForm.invoiceStatus],
+  () => [searchForm.keyword, searchForm.dateRange, searchForm.invoiceStatus],
   () => {
     pagination.page = 1;
     loadStatementInvoice();
   }
 );
 
+// 搜索
+const handleSearch = () => {
+  pagination.page = 1;
+  loadStatementInvoice();
+};
+
+// 重置
+const handleReset = () => {
+  searchForm.keyword = '';
+  searchForm.dateRange = [];
+  searchForm.invoiceStatus = '';
+  pagination.page = 1;
+  loadStatementInvoice();
+};
+
 // 页面加载时获取数据
 onMounted(() => {
   // loadDictData();
@@ -123,35 +174,30 @@ const handleView = (row: any) => {
 </script>
 
 <style scoped>
-.bottom-bar {
-  margin-top: 16px;
+.search-area {
+  margin-bottom: 16px;
   padding: 16px;
-  background: #fafafa;
-  border: 1px solid #eee;
+  background: #fff;
+  border: 1px solid #e4e7ed;
   border-radius: 4px;
-  display: flex;
-  justify-content: flex-end;
 }
 
-.summary {
+.search-area .el-form {
   display: flex;
-  align-items: center;
-  gap: 20px;
-}
-
-.summary span {
-  font-size: 14px;
-  color: #666;
+  flex-wrap: wrap;
+  gap: 16px;
+  align-items: flex-end;
 }
 
-.summary em {
-  color: #e60012;
-  font-style: normal;
-  font-weight: bold;
+.search-area .el-form-item {
+  margin-bottom: 0;
+  margin-right: 0;
 }
 
-.summary .total em {
-  font-size: 16px;
+.search-area .el-input,
+.search-area .el-select,
+.search-area .el-date-picker {
+  width: 200px;
 }
 
 .pagination-wrapper {

+ 126 - 27
src/views/valueAdded/complaint/index.vue

@@ -24,16 +24,28 @@
 
       <!-- 上传照片 -->
       <el-form-item label="上传照片">
-        <el-upload
-          class="avatar-uploader"
-          :action="action"
-          :show-file-list="false"
-          :on-success="handleAvatarSuccess"
-          :before-upload="beforeAvatarUpload"
-        >
-          <el-icon class="avatar-uploader-icon"><Plus /></el-icon>
-        </el-upload>
-        <img class="upload-img" v-if="form.relatedPictures" :src="form.relatedPictures" />
+        <div class="upload-row">
+          <el-upload
+            v-if="!form.relatedPictures || form.relatedPictures.length < 3"
+            class="upload-box"
+            :action="action"
+            :show-file-list="false"
+            :on-success="handleAvatarSuccess"
+            :before-upload="beforeAvatarUpload"
+            accept="image/*"
+          >
+            <div class="upload-placeholder">
+              <el-icon :size="24"><Plus /></el-icon>
+            </div>
+          </el-upload>
+          <div v-if="form.relatedPictures && form.relatedPictures.length > 0" class="image-list">
+            <div v-for="(img, imgIndex) in form.relatedPictures" :key="imgIndex" class="image-item">
+              <el-image :src="img" fit="cover" style="width: 100px; height: 100px; border-radius: 6px" />
+              <el-icon class="delete-icon" @click="handleDeleteImage(imgIndex as any)"><Close /></el-icon>
+            </div>
+          </div>
+        </div>
+        <div class="upload-tip">最多上传3张图片</div>
       </el-form-item>
 
       <!-- 联系方式 -->
@@ -51,15 +63,15 @@
 
 <script setup lang="ts">
 import { reactive } from 'vue';
-import { Plus } from '@element-plus/icons-vue';
+import { Plus, Close } from '@element-plus/icons-vue';
 import { ElMessage } from 'element-plus';
 import { PageTitle } from '@/components';
 import { addComplaintsSuggestions } from '@/api/pc/valueAdded';
+import { onPath } from '@/utils/siteConfig';
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const { complaints_suggestion_type } = toRefs<any>(proxy?.useDict('complaints_suggestion_type'));
 const action = import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload';
 import type { UploadProps } from 'element-plus';
-
 // 反馈类型选项
 const feedbackTypes = computed(() => complaints_suggestion_type.value || []);
 
@@ -67,7 +79,7 @@ const feedbackTypes = computed(() => complaints_suggestion_type.value || []);
 const form = reactive({
   feedbackType: '0',
   feedbackContent: '',
-  relatedPictures: '',
+  relatedPictures: [] as string[],
   phone: ''
 });
 const rules = {
@@ -75,13 +87,17 @@ const rules = {
   phone: [{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }]
 };
 
-// 上传图片
-const handleUploadChange = (file, fileList) => {};
-
 //上传成功
 const handleAvatarSuccess = (res: any) => {
   if (res.code == 200) {
-    form.relatedPictures = res.data.url;
+    if (!form.relatedPictures) {
+      form.relatedPictures = [];
+    }
+    if (form.relatedPictures.length >= 3) {
+      ElMessage.warning('最多上传3张图片');
+      return;
+    }
+    form.relatedPictures.push(res.data.url);
   } else {
     ElMessage({
       message: res.msg,
@@ -91,9 +107,19 @@ const handleAvatarSuccess = (res: any) => {
   console.log(res);
 };
 
+// 删除图片
+const handleDeleteImage = (imageIndex: number) => {
+  form.relatedPictures.splice(imageIndex, 1);
+};
+
 const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
+  const isImage = rawFile.type.startsWith('image/');
+  if (!isImage) {
+    ElMessage.error('只能上传图片文件!');
+    return false;
+  }
   if (rawFile.size / 1024 / 1024 > 2) {
-    ElMessage.error('不能大于2MB!');
+    ElMessage.error('图片大小不能超过2MB!');
     return false;
   }
   return true;
@@ -105,8 +131,12 @@ const handleSubmit = async () => {
     return;
   }
   try {
-    await addComplaintsSuggestions(form);
-    ElMessage.success('提交成功');
+    const submitData = {
+      ...form,
+      relatedPictures: form.relatedPictures && form.relatedPictures.length > 0 ? form.relatedPictures.join(',') : ''
+    };
+    await addComplaintsSuggestions(submitData);
+    onPath('/order/afterSale?tab=complaint');
   } catch (error) {
     ElMessage.error('提交失败');
   }
@@ -140,21 +170,90 @@ const handleSubmit = async () => {
   }
 }
 
-:deep(.el-upload--picture-card) {
-  width: 100px;
-  height: 100px;
+.upload-row {
+  display: flex;
+  align-items: flex-start;
+  gap: 10px;
+  flex-wrap: wrap;
+}
+
+.upload-box {
+  :deep(.el-upload) {
+    width: 100px;
+    height: 100px;
+    border: 1px dashed #d9d9d9;
+    border-radius: 6px;
+    cursor: pointer;
+    overflow: hidden;
+
+    &:hover {
+      border-color: #e60012;
+    }
+  }
+
+  .upload-placeholder {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    color: #999;
+    font-size: 12px;
+    gap: 5px;
+  }
+
+  .upload-preview {
+    width: 100%;
+    height: 100%;
+
+    .el-image {
+      width: 100%;
+      height: 100%;
+    }
+  }
 }
 
-:deep(.el-upload-list--picture-card .el-upload-list__item) {
-  width: 100px;
-  height: 100px;
+.image-list {
+  display: flex;
+  gap: 10px;
+  flex-wrap: wrap;
+  margin-top: 0;
+
+  .image-item {
+    position: relative;
+    width: 100px;
+    height: 100px;
+    flex-shrink: 0;
+
+    .delete-icon {
+      position: absolute;
+      top: 2px;
+      right: 2px;
+      width: 20px;
+      height: 20px;
+      background: rgba(0, 0, 0, 0.5);
+      color: #fff;
+      border-radius: 50%;
+      cursor: pointer;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-size: 14px;
+
+      &:hover {
+        background: rgba(0, 0, 0, 0.7);
+      }
+    }
+  }
 }
 
 .upload-tip {
   font-size: 12px;
   color: #999;
-  text-align: center;
+  margin-top: 5px;
 }
+
 .tag-group {
   display: flex;
   flex-wrap: wrap;

+ 196 - 65
src/views/valueAdded/maintenance/apply.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="maintenance-apply-container">
     <div class="page-header">
-      <div class="header-left">维保申请</div>
+      <div class="header-left">{{ pageTitle }}</div>
       <div class="header-right" @click="handleBack">返回</div>
     </div>
 
@@ -25,24 +25,29 @@
       </div>
 
       <div class="form-container">
-        <el-form :model="form" :rules="rules" ref="formRef" label-width="120px" class="apply-form" hide-required-asterisk>
+        <el-form :model="form" :rules="mode === 'view' ? {} : rules" ref="formRef" label-width="120px" class="apply-form" hide-required-asterisk>
           <el-form-item label="申请人名称:" prop="applicantName">
-            <el-input v-model="form.applicantName" placeholder="请输入申请人名称" clearable />
+            <el-input v-model="form.applicantName" placeholder="请输入申请人名称" clearable :disabled="mode === 'view'" />
           </el-form-item>
 
-          <!-- The mockup shows a validation error explicitly for phone -->
-          <el-form-item label="手机号码:" prop="phone" class="phone-item">
-            <el-input v-model="form.phone" placeholder="请输入申请人手机号码" clearable />
+          <!-- The mockup shows a validation error explicitly for applicantPhone -->
+          <el-form-item label="手机号码:" prop="applicantPhone" class="applicantPhone-item">
+            <el-input v-model="form.applicantPhone" placeholder="请输入申请人手机号码" clearable :disabled="mode === 'view'" />
           </el-form-item>
 
-          <el-form-item label="验证码:" prop="verifyCode">
+          <el-form-item label="验证码:" prop="verifyCode" v-if="mode !== 'view'">
             <div class="verify-code-wrapper">
-              <el-input v-model="form.verifyCode" placeholder="短信验证码" class="code-input" clearable />
-              <div class="send-btn" @click="handleSendCode">发送验证码</div>
+              <el-input v-model="form.verifyCode" placeholder="短信验证码" class="code-input" clearable>
+                <template #suffix>
+                  <span @click="handleSendCode" :class="['code', countdown > 0 ? 'disabled' : '']">
+                    {{ codeText }}
+                  </span>
+                </template>
+              </el-input>
             </div>
           </el-form-item>
 
-          <el-form-item label="">
+          <!-- <el-form-item label="">
             <div class="drag-verify-bar" @click="handleVerify">
               <div class="verify-btn" :class="{ verified: isVerified }">
                 <div class="inner-dot" v-if="!isVerified"></div>
@@ -50,43 +55,44 @@
               </div>
               <div class="verify-text">{{ isVerified ? '验证通过' : '点击验证' }}</div>
             </div>
-          </el-form-item>
+          </el-form-item> -->
 
           <el-form-item label="服务时间:" prop="serviceTime">
-            <el-select v-model="form.serviceTime" placeholder="请选择服务时间" class="w-full" filterable>
+            <el-select v-model="form.serviceTime" placeholder="请选择服务时间" class="w-full" filterable :disabled="mode === 'view'">
               <el-option v-for="dict in service_time_type" :key="dict.value" :label="dict.label" :value="dict.value" />
             </el-select>
           </el-form-item>
 
-          <el-form-item label="月度维保次数:" prop="monthlyTimes">
-            <el-input v-model="form.monthlyTimes" placeholder="请输入月度维保次数" clearable />
+          <el-form-item label="月度维保次数:" prop="monthMainten">
+            <el-input v-model="form.monthMainten" placeholder="请输入月度维保次数" clearable :disabled="mode === 'view'" />
           </el-form-item>
 
           <el-form-item label="维保类型:" prop="maintainType">
-            <el-select v-model="form.maintainType" placeholder="请选择" class="w-full" filterable>
+            <el-select v-model="form.maintainType" placeholder="请选择" class="w-full" filterable :disabled="mode === 'view'">
               <el-option v-for="dict in maintenance_type" :key="dict.value" :label="dict.label" :value="dict.value" />
             </el-select>
           </el-form-item>
 
-          <el-form-item label="服务内容:" prop="serviceContent">
+          <el-form-item label="服务内容:" prop="serviceContentArr">
             <div class="service-content-wrapper">
               <div class="service-tags">
-                <div class="tag-item" :class="{ active: form.serviceContent.includes('express') }" @click="toggleServiceContent('express')">
-                  急速快递
-                </div>
-                <div class="tag-item" :class="{ active: form.serviceContent.includes('repair') }" @click="toggleServiceContent('repair')">
-                  售后维修
-                </div>
-                <div class="tag-item" :class="{ active: form.serviceContent.includes('warranty') }" @click="toggleServiceContent('warranty')">
-                  产品质保
-                </div>
+                <el-checkbox-group v-model="form.serviceContentArr" :disabled="mode === 'view'">
+                  <el-checkbox v-for="item in serviceContentList" :key="item.id" :label="item.id">{{ item.itemName }}</el-checkbox>
+                </el-checkbox-group>
               </div>
-              <el-input v-model="form.otherService" type="textarea" :rows="4" placeholder="请输入其他服务" class="other-service-input" />
+              <el-input
+                v-model="form.otherService"
+                type="textarea"
+                :rows="4"
+                placeholder="请输入其他服务"
+                class="other-service-input"
+                :disabled="mode === 'view'"
+              />
             </div>
           </el-form-item>
 
-          <el-form-item>
-            <el-button type="primary" class="next-btn" @click="handleNext">下一步</el-button>
+          <el-form-item v-if="mode !== 'view'">
+            <el-button type="primary" class="next-btn" @click="handleNext">{{ mode === 'edit' ? '保存修改' : '下一步' }}</el-button>
           </el-form-item>
         </el-form>
       </div>
@@ -95,30 +101,53 @@
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, onMounted } from 'vue';
-import { useRouter } from 'vue-router';
+import { smsCode } from '@/api/breg/index';
+import { addMaintainInfo, updateMaintainInfo, listServerItem, getMaintainInfo } from '@/api/pc/valueAdded';
+import { ref, reactive, onMounted, computed } from 'vue';
+import { useRouter, useRoute } from 'vue-router';
 import { Check } from '@element-plus/icons-vue';
 import { ElMessage } from 'element-plus';
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const { service_time_type, maintenance_type } = toRefs<any>(proxy?.useDict('service_time_type', 'maintenance_type'));
 
 const router = useRouter();
+const route = useRoute();
 const formRef = ref();
 
-const isVerified = ref(false);
+// 模式:add-新增,view-查看,edit-修改
+const mode = ref<'add' | 'view' | 'edit'>('add');
+
+// 根据模式显示不同的页面标题
+const pageTitle = computed(() => {
+  switch (mode.value) {
+    case 'view':
+      return '查看维保';
+    case 'edit':
+      return '修改维保';
+    default:
+      return '维保申请';
+  }
+});
 
+const isVerified = ref(false);
+const codeText = ref<string>('发送验证码');
+const countdown = ref<number>(0);
+const timer = ref<any>(null);
+const serviceContentList = ref<any[]>([]);
 const form = reactive({
+  id: null,
   applicantName: '',
-  phone: '',
+  applicantPhone: '',
   verifyCode: '',
   serviceTime: '',
-  monthlyTimes: '',
+  monthMainten: '',
   maintainType: '',
-  serviceContent: [] as string[],
+  serviceContentArr: [],
+  serviceContent: '',
   otherService: ''
 });
 
-// To perfectly match the mockup which shows a persistent validation error on phone
+// To perfectly match the mockup which shows a persistent validation error on applicantPhone
 const validatePhone = (rule: any, value: string, callback: any) => {
   if (!value) {
     callback(new Error('请输入手机号码'));
@@ -131,59 +160,152 @@ const validatePhone = (rule: any, value: string, callback: any) => {
 
 const rules = {
   applicantName: [{ required: true, message: '请输入申请人名称', trigger: 'blur' }],
-  phone: [{ required: true, validator: validatePhone, trigger: 'blur' }],
+  applicantPhone: [{ required: true, validator: validatePhone, trigger: 'blur' }],
   verifyCode: [{ required: true, message: '请输入短信验证码', trigger: 'blur' }],
   serviceTime: [{ required: true, message: '请选择服务时间', trigger: 'change' }],
-  monthlyTimes: [{ required: true, message: '请输入月度维保次数', trigger: 'blur' }],
+  monthMainten: [{ required: true, message: '请输入月度维保次数', trigger: 'blur' }],
   maintainType: [{ required: true, message: '请选择维保类型', trigger: 'change' }]
 };
 
 const handleBack = () => {
   router.back();
 };
-
+// 启动倒计时
+const startCountdown = () => {
+  countdown.value = 60;
+  codeText.value = `${countdown.value}s 后重新发送`;
+
+  timer.value = setInterval(() => {
+    countdown.value--;
+    if (countdown.value > 0) {
+      codeText.value = `${countdown.value}s 后重新发送`;
+    } else {
+      clearInterval(timer.value);
+      timer.value = null;
+      codeText.value = '发送验证码';
+    }
+  }, 1000);
+};
 const handleSendCode = () => {
-  if (!form.phone) {
-    ElMessage.warning('请先输入手机号码');
+  // 防止倒计时期间重复点击
+  if (countdown.value > 0) return;
+
+  const applicantPhone = form.applicantPhone;
+
+  // 1. 基础格式校验
+  if (!validateMobile(applicantPhone)) {
+    ElMessage({
+      message: '请输入正确的手机号码',
+      type: 'warning'
+    });
     return;
   }
-  ElMessage.success('验证码已发送');
+
+  // 2. 【新增】先调用接口查询用户是否存在
+
+  smsCode({ phonenumber: applicantPhone })
+    .then((smsRes: any) => {
+      if (smsRes.code === 200) {
+        ElMessage({
+          message: '验证码已发送',
+          type: 'success'
+        });
+        startCountdown(); // 开始倒计时
+      } else {
+        // 发送短信接口业务失败
+        ElMessage.error(smsRes.msg || '发送验证码失败');
+      }
+    })
+    .catch((err: any) => {
+      // 发送短信接口网络错误或异常
+      ElMessage.error(err.msg || '发送验证码请求异常');
+    });
 };
 
+// 验证手机号
+const validateMobile = (applicantPhone: any) => {
+  const reg = /^1[3-9]\d{9}$/;
+  return reg.test(applicantPhone);
+};
 const handleVerify = () => {
   isVerified.value = true;
 };
 
-const toggleServiceContent = (type: string) => {
-  const index = form.serviceContent.indexOf(type);
-  if (index > -1) {
-    form.serviceContent.splice(index, 1);
-  } else {
-    form.serviceContent.push(type);
+const handleNext = async () => {
+  const valid = await formRef.value?.validate();
+  if (!valid) return;
+  if (valid) {
+    // 将服务内容字符串(ID列表)转换为数组
+    form.serviceContent = Array.isArray(form.serviceContentArr) ? form.serviceContentArr.join(',') : form.serviceContentArr;
+    if (form.id) {
+      await updateMaintainInfo(form);
+      ElMessage.success('修改成功');
+    } else {
+      await addMaintainInfo(form);
+      ElMessage.success('提交成功');
+    }
+    router.back();
   }
 };
 
-const handleNext = async () => {
-  if (!formRef.value) return;
-  await formRef.value.validate((valid: boolean) => {
-    if (valid) {
-      if (!isVerified.value) {
-        ElMessage.warning('请先点击验证');
-        return;
+// 加载维保详情
+const loadMaintainInfo = async (id: string) => {
+  try {
+    const res = await getMaintainInfo(id as any);
+    if (res.code === 200 && res.data) {
+      const data = res.data;
+      form.id = data.id;
+      form.applicantName = data.applicantName || '';
+      form.applicantPhone = data.applicantPhone || '';
+      form.serviceTime = data.serviceTime || '';
+      form.monthMainten = data.monthMainten || '';
+      form.maintainType = data.maintainType || '';
+      form.otherService = data.otherService || '';
+
+      // 将服务内容字符串(逗号拼接的id)转换为数字数组
+      if (data.serviceContent) {
+        const serviceIds = data.serviceContent.split(',');
+        form.serviceContentArr = serviceIds;
+      } else {
+        form.serviceContentArr = [];
       }
-      ElMessage.success('提交成功');
-      // router.push('...');
     }
-  });
+  } catch (error) {
+    ElMessage.error('加载维保详情失败');
+  }
 };
+// 加载服务内容列表
+const loadServiceContentList = async () => {
+  try {
+    const res = await listServerItem({});
+    serviceContentList.value = res.rows || [];
+  } catch (error) {
+    ElMessage.error('加载服务内容列表失败');
+  }
+};
+onMounted(async () => {
+  // 先加载服务内容列表,确保复选框选项可用
+  await loadServiceContentList();
 
-onMounted(() => {
-  // To trigger the phone error message immediately as shown in the mockup
-  setTimeout(() => {
-    if (formRef.value) {
-      formRef.value.validateField('phone').catch(() => {});
-    }
-  }, 500);
+  // 从路由参数获取模式和ID
+  const queryMode = route.query.mode as string;
+  const queryId = route.query.id as string;
+
+  if (queryMode && (queryMode === 'view' || queryMode === 'edit')) {
+    mode.value = queryMode;
+  }
+
+  // 如果有ID,加载维保详情
+  if (queryId) {
+    await loadMaintainInfo(queryId);
+  } else {
+    // To trigger the applicantPhone error message immediately as shown in the mockup
+    setTimeout(() => {
+      if (formRef.value) {
+        formRef.value.validateField('applicantPhone').catch(() => {});
+      }
+    }, 500);
+  }
 });
 </script>
 
@@ -445,7 +567,16 @@ onMounted(() => {
     width: 100%;
   }
 }
+.code {
+  font-size: 14px;
+  color: #e7000b;
+  cursor: pointer;
 
+  &.disabled {
+    color: #999;
+    cursor: not-allowed;
+  }
+}
 .next-btn {
   // background-color: #0fb881;
   // border-color: #0fb881;
@@ -463,7 +594,7 @@ onMounted(() => {
 }
 
 /* Override error styles to match exactly the mockup */
-:deep(.phone-item.is-error) {
+:deep(.applicantPhone-item.is-error) {
   .el-input__wrapper {
     box-shadow: 0 0 0 1px #f56c6c inset !important;
   }

+ 91 - 3
src/views/valueAdded/maintenance/index.vue

@@ -1,8 +1,34 @@
 <template>
   <div class="maintenance-container">
     <PageTitle title="维保服务" />
+    <el-button type="danger" plain @click="handleApply" style="float: right; margin-bottom: 10px">新增维保</el-button>
+    <div v-if="tableData.length">
+      <el-table v-loading="loading" :data="tableData" border style="width: 100%" ref="tableRef">
+        <el-table-column prop="applicantName" label="申请人名称" min-width="130" align="center" />
+        <el-table-column prop="applicantPhone" label="手机号码" min-width="110" align="center" />
+        <el-table-column prop="serviceTime" label="服务时间" align="center">
+          <template #default="{ row }">
+            {{ getDictLabel(service_time_type, row.serviceTime) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="monthMainten" label="月度维保次数" align="center" />
 
-    <div class="empty-state">
+        <el-table-column prop="maintainType" label="维保类型" min-width="90" align="center">
+          <template #default="{ row }">
+            {{ getDictLabel(maintenance_type, row.maintainType) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="serviceContentStr" label="维保类型" align="center" />
+
+        <el-table-column label="操作" width="100" align="center">
+          <template #default="{ row }">
+            <el-button type="primary" link size="small" @click="handleView(row)">查看</el-button>
+            <el-button type="danger" link size="small" @click="handleUpdate(row)">修改</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+    <div class="empty-state" v-else>
       <div class="empty-icon">
         <img
           src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 160'%3E%3Crect fill='%23f5f5f5' width='200' height='160' rx='8'/%3E%3Crect x='50' y='60' width='100' height='70' fill='%23e0e0e0' rx='4'/%3E%3Crect x='60' y='70' width='80' height='8' fill='%23ccc'/%3E%3Crect x='60' y='85' width='60' height='6' fill='%23ccc'/%3E%3Crect x='60' y='98' width='70' height='6' fill='%23ccc'/%3E%3Cpath d='M85 45 L95 55 L115 35' stroke='%23ccc' stroke-width='4' fill='none'/%3E%3C/svg%3E"
@@ -16,14 +42,76 @@
 </template>
 
 <script setup lang="ts">
+import { getMaintainInfoList, listServerItem } from '@/api/pc/valueAdded';
 import { PageTitle } from '@/components';
 import { useRouter } from 'vue-router';
-
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { service_time_type, maintenance_type } = toRefs<any>(proxy?.useDict('service_time_type', 'maintenance_type'));
 const router = useRouter();
-
+const pagination = reactive({ page: 1, pageSize: 5, total: 0 });
+const tableData = ref<any[]>([]);
+const loading = ref(false);
+const serviceContentList = ref<any[]>([]);
 const handleApply = () => {
   router.push(`/valueAdded/maintenanceApply`);
 };
+
+// 查看维保详情
+const handleView = (row: any) => {
+  router.push({
+    path: `/valueAdded/maintenanceApply`,
+    query: { id: row.id, mode: 'view' }
+  });
+};
+
+// 修改维保信息
+const handleUpdate = (row: any) => {
+  router.push({
+    path: `/valueAdded/maintenanceApply`,
+    query: { id: row.id, mode: 'edit' }
+  });
+};
+
+// 加载对账单列表
+const loadMaintainInfoList = async () => {
+  try {
+    loading.value = true;
+    const queryParams = {
+      pageNum: pagination.page,
+      pageSize: pagination.pageSize
+    };
+
+    const res = await getMaintainInfoList(queryParams);
+
+    if (res.code === 200 && res.rows) {
+      tableData.value = res.rows;
+      pagination.total = res.total || 0;
+    }
+  } catch (error) {
+    ElMessage.error('加载维保列表失败');
+  } finally {
+    loading.value = false;
+  }
+};
+const getDictLabel = (dictOptions: any[], value: string) => {
+  if (!dictOptions || !value) return value;
+  const dict = dictOptions.find((item) => item.value === value);
+  return dict ? dict.label : value;
+};
+
+// 加载服务内容列表
+const loadServiceContentList = async () => {
+  try {
+    const res = await listServerItem({});
+    serviceContentList.value = res.rows || [];
+  } catch (error) {
+    ElMessage.error('加载服务内容列表失败');
+  }
+};
+onMounted(() => {
+  loadServiceContentList();
+  loadMaintainInfoList();
+});
 </script>
 
 <style scoped lang="scss">