hurx 1 kuukausi sitten
vanhempi
sitoutus
9c4c72abbd

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

@@ -185,8 +185,11 @@ const total = ref(0);
 const deptList = ref([]);
 
 const mainTabs = [
-  { key: 'myAudit', label: '我审批的', icon: Document },
-  { key: 'myApply', label: '我申请的', icon: User }
+  { key: 'myAudit', label: '我审批的', icon: Document }
+  // { key: 'myApply', label: '我申请的', icon: User }
+
+  // { key: 'myAudit', label: '待审批', icon: Document },
+  // { key: 'myApply', label: '已审批', icon: User }
 ];
 
 const auditStatusTabs = [

+ 16 - 12
src/views/order/orderManage/applyAfter.vue

@@ -20,10 +20,10 @@
           </div>
         </div>
 
-        <div class="header-right">
+        <!-- <div class="header-right">
           <el-button @click="handleViewEvaluation">查看评价</el-button>
           <el-button @click="handleBuyAgain">再次购买</el-button>
-        </div>
+        </div> -->
       </div>
     </div>
 
@@ -41,13 +41,13 @@
 
           <div class="section-title">提交数量</div>
 
-          <div class="summary-bar" v-if="selectedProducts.length > 0">
+          <div class="summary-bar" v-if="topBtnFlag">
             <div class="summary-left">
               <span>商品总数:{{ summary.totalKinds }}个</span>
               <span>本单提交商品总数量:{{ summary.totalQty }}个</span>
               <span>退货总金额:¥ {{ summary.totalAmount }}</span>
             </div>
-            <el-icon class="summary-close" @click="handleClearProducts"><Close /></el-icon>
+            <el-icon class="summary-close" @click="topBtnFlag = false"><Close /></el-icon>
           </div>
 
           <div class="product-table">
@@ -149,8 +149,17 @@
             </div>
           </div>
 
-          <div class="pickup-info" v-else>
-            <div class="pickup-row hint">请在提交后按提示将商品寄回,售后专员将与您确认寄送信息。</div>
+          <div class="pickup-info" v-if="form.returnMethod === '2'">
+            <div class="pickup-row"><span class="k">邮寄地址:</span><span class="v"></span></div>
+            <div class="pickup-row">
+              <span class="k">取件地址:</span><span class="v">{{ form.chargebackAddress }}</span>
+            </div>
+            <div class="pickup-row">
+              <span class="k">收件人:</span><span class="v">{{ form.chargebackName }} {{ form.chargebackPhone }}</span>
+            </div>
+            <div class="pickup-info">
+              <div class="pickup-row hint">提交服务单后,售后专员可能与您电话沟通,请保持手机畅通</div>
+            </div>
           </div>
         </div>
 
@@ -199,8 +208,7 @@ import { getOrderInfo, getOrderProductsWithAvailableQty, getReturnReason } from
 import { getEnterpriseInfo, getAddressList } from '@/api/pc/enterprise';
 import { getOrderReturnInfo, addOrderReturn } from '@/api/pc/enterprise/orderReturn';
 import type { OrderReturn } from '@/api/pc/enterprise/orderReturnTypes';
-import { el } from 'element-plus/es/locale/index.mjs';
-
+const topBtnFlag = ref(true);
 const router = useRouter();
 const route = useRoute();
 const loading = ref(false);
@@ -390,10 +398,6 @@ const loadEnterpriseInfo = async () => {
   }
 };
 
-const handleClearProducts = () => {
-  selectedProducts.value = [];
-};
-
 const handleChangeAddress = () => {
   addressDialogVisible.value = true;
 };

+ 472 - 0
src/views/valueAdded/maintenance/apply.vue

@@ -0,0 +1,472 @@
+<template>
+  <div class="maintenance-apply-container">
+    <div class="page-header">
+      <div class="header-left">维保申请</div>
+      <div class="header-right" @click="handleBack">返回</div>
+    </div>
+
+    <div class="content-wrapper">
+      <div class="custom-steps-container">
+        <div class="step-list">
+          <div class="step-item active">
+            <div class="step-icon">
+              <el-icon><Check /></el-icon>
+            </div>
+            <div class="step-title">提交维保申请</div>
+          </div>
+          <div class="step-line active-line"></div>
+          <div class="step-item">
+            <div class="step-icon normal">
+              <span>2</span>
+            </div>
+            <div class="step-title normal-text">审核通过开始服务</div>
+          </div>
+        </div>
+      </div>
+
+      <div class="form-container">
+        <el-form :model="form" :rules="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-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 />
+          </el-form-item>
+
+          <el-form-item label="验证码:" prop="verifyCode">
+            <div class="verify-code-wrapper">
+              <el-input v-model="form.verifyCode" placeholder="短信验证码" class="code-input" clearable />
+              <div class="send-btn" @click="handleSendCode">发送验证码</div>
+            </div>
+          </el-form-item>
+
+          <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>
+                <el-icon v-else color="#0fb881"><Check /></el-icon>
+              </div>
+              <div class="verify-text">{{ isVerified ? '验证通过' : '点击验证' }}</div>
+            </div>
+          </el-form-item>
+
+          <el-form-item label="服务时间:" prop="serviceTime">
+            <el-select v-model="form.serviceTime" placeholder="请选择服务时间" class="w-full" filterable>
+              <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>
+
+          <el-form-item label="维保类型:" prop="maintainType">
+            <el-select v-model="form.maintainType" placeholder="请选择" class="w-full" filterable>
+              <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">
+            <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>
+              </div>
+              <el-input v-model="form.otherService" type="textarea" :rows="4" placeholder="请输入其他服务" class="other-service-input" />
+            </div>
+          </el-form-item>
+
+          <el-form-item>
+            <el-button type="primary" class="next-btn" @click="handleNext">下一步</el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, onMounted } from 'vue';
+import { useRouter } 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 formRef = ref();
+
+const isVerified = ref(false);
+
+const form = reactive({
+  applicantName: '',
+  phone: '',
+  verifyCode: '',
+  serviceTime: '',
+  monthlyTimes: '',
+  maintainType: '',
+  serviceContent: [] as string[],
+  otherService: ''
+});
+
+// To perfectly match the mockup which shows a persistent validation error on phone
+const validatePhone = (rule: any, value: string, callback: any) => {
+  if (!value) {
+    callback(new Error('请输入手机号码'));
+  } else if (!/^1[3-9]\d{9}$/.test(value)) {
+    callback(new Error('手机号码格式不正确'));
+  } else {
+    callback();
+  }
+};
+
+const rules = {
+  applicantName: [{ required: true, message: '请输入申请人名称', trigger: 'blur' }],
+  phone: [{ required: true, validator: validatePhone, trigger: 'blur' }],
+  verifyCode: [{ required: true, message: '请输入短信验证码', trigger: 'blur' }],
+  serviceTime: [{ required: true, message: '请选择服务时间', trigger: 'change' }],
+  monthlyTimes: [{ required: true, message: '请输入月度维保次数', trigger: 'blur' }],
+  maintainType: [{ required: true, message: '请选择维保类型', trigger: 'change' }]
+};
+
+const handleBack = () => {
+  router.back();
+};
+
+const handleSendCode = () => {
+  if (!form.phone) {
+    ElMessage.warning('请先输入手机号码');
+    return;
+  }
+  ElMessage.success('验证码已发送');
+};
+
+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 () => {
+  if (!formRef.value) return;
+  await formRef.value.validate((valid: boolean) => {
+    if (valid) {
+      if (!isVerified.value) {
+        ElMessage.warning('请先点击验证');
+        return;
+      }
+      ElMessage.success('提交成功');
+      // router.push('...');
+    }
+  });
+};
+
+onMounted(() => {
+  // To trigger the phone error message immediately as shown in the mockup
+  setTimeout(() => {
+    if (formRef.value) {
+      formRef.value.validateField('phone').catch(() => {});
+    }
+  }, 500);
+});
+</script>
+
+<style scoped lang="scss">
+.maintenance-apply-container {
+  min-height: 100vh;
+  background-color: #fff;
+}
+
+.page-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 0 40px;
+  height: 60px;
+  border-bottom: 1px solid #f0f0f0;
+
+  .header-left {
+    font-size: 16px;
+    color: #333;
+    font-weight: 500;
+  }
+
+  .header-right {
+    font-size: 14px;
+    color: #666;
+    cursor: pointer;
+
+    &:hover {
+      color: #333;
+    }
+  }
+}
+
+.content-wrapper {
+  max-width: 800px;
+  margin: 0 auto;
+  padding: 40px 20px;
+}
+
+/* Steps Styling */
+.custom-steps-container {
+  margin-bottom: 60px;
+  display: flex;
+  justify-content: center;
+
+  .step-list {
+    display: flex;
+    align-items: flex-start;
+    width: 600px;
+    justify-content: space-between;
+  }
+
+  .step-item {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    position: relative;
+    z-index: 2;
+    background: #fff;
+    padding: 0 10px;
+    width: 140px;
+
+    .step-icon {
+      width: 40px;
+      height: 40px;
+      border-radius: 50%;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      font-size: 20px;
+      border: 1px solid #e60012;
+      color: #e60012;
+      background-color: #fff;
+      margin-bottom: 10px;
+
+      &.normal {
+        border-color: #d9d9d9;
+        color: #b3b3b3;
+        font-size: 18px;
+        font-weight: bold;
+      }
+    }
+
+    .step-title {
+      font-size: 14px;
+      color: #e60012;
+      font-weight: 500;
+      white-space: nowrap;
+
+      &.normal-text {
+        color: #999;
+      }
+    }
+  }
+
+  .step-line {
+    flex: 1;
+    height: 2px;
+    background-color: #e60012;
+    margin: 0 -20px;
+    margin-top: 19px;
+    z-index: 1;
+
+    &.active-line {
+      background-color: #e60012;
+    }
+  }
+}
+
+/* Form Styling */
+.form-container {
+  display: flex;
+  justify-content: center;
+
+  .apply-form {
+    width: 500px;
+  }
+
+  :deep(.el-form-item__label) {
+    font-size: 14px;
+    color: #333;
+    font-weight: normal;
+  }
+
+  .full-width {
+    width: 200px;
+  }
+}
+
+.verify-code-wrapper {
+  display: flex;
+  align-items: center;
+  width: 100%;
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+  overflow: hidden;
+  transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
+
+  &:focus-within {
+    border-color: #409eff;
+  }
+
+  .code-input {
+    flex: 1;
+    :deep(.el-input__wrapper) {
+      border-radius: 0;
+      box-shadow: none !important;
+      background-color: transparent;
+    }
+  }
+
+  .send-btn {
+    padding: 0 15px;
+    color: #666;
+    font-size: 13px;
+    cursor: pointer;
+    border-left: 1px solid #dcdfe6;
+    background: #fafafa;
+    height: 32px;
+    line-height: 32px;
+
+    &:hover {
+      color: #409eff;
+    }
+  }
+}
+
+.drag-verify-bar {
+  width: 100%;
+  height: 34px;
+  background-color: #f0f5ff;
+  border: 1px solid #c6d8f5;
+  border-radius: 4px;
+  display: flex;
+  align-items: center;
+  cursor: pointer;
+  position: relative;
+
+  .verify-btn {
+    width: 32px;
+    height: 32px;
+    background-color: #fff;
+    border-radius: 50%;
+    border: 1px solid #c6d8f5;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    margin-left: -1px;
+    box-shadow: 0 0 5px rgba(0, 0, 0, 0.05);
+    position: absolute;
+    left: 10px;
+
+    .inner-dot {
+      width: 10px;
+      height: 10px;
+      background-color: #a4c2f4;
+      border-radius: 50%;
+    }
+
+    &.verified {
+      left: auto;
+      right: -1px;
+      border-color: #0fb881;
+    }
+  }
+
+  .verify-text {
+    flex: 1;
+    text-align: center;
+    font-size: 13px;
+    color: #666;
+    user-select: none;
+  }
+
+  &.success {
+    background-color: #e5f7f1;
+    border-color: #0fb881;
+
+    .verify-text {
+      color: #0fb881;
+    }
+  }
+}
+
+.service-content-wrapper {
+  width: 100%;
+
+  .service-tags {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 10px;
+    margin-bottom: 12px;
+
+    .tag-item {
+      padding: 6px 16px;
+      border: 1px solid #dcdfe6;
+      border-radius: 4px;
+      font-size: 13px;
+      color: #666;
+      cursor: pointer;
+      transition: all 0.3s;
+
+      &.active {
+        border-color: #409eff;
+        color: #409eff;
+        background-color: #ecf5ff;
+      }
+
+      &:hover {
+        border-color: #c0c4cc;
+      }
+    }
+  }
+
+  .other-service-input {
+    width: 100%;
+  }
+}
+
+.next-btn {
+  background-color: #0fb881;
+  border-color: #0fb881;
+  color: #fff;
+  padding: 8px 25px;
+  border-radius: 2px;
+  font-weight: normal;
+
+  &:hover,
+  &:focus {
+    background-color: #12cc90;
+    border-color: #12cc90;
+    color: #fff;
+  }
+}
+
+/* Override error styles to match exactly the mockup */
+:deep(.phone-item.is-error) {
+  .el-input__wrapper {
+    box-shadow: 0 0 0 1px #f56c6c inset !important;
+  }
+  .el-form-item__error {
+    color: #e60012; /* matching the red error text color in the mockup */
+  }
+}
+</style>

+ 8 - 1
src/views/valueAdded/maintenance/index.vue

@@ -10,13 +10,20 @@
         />
       </div>
       <p class="empty-text">亲!您还未体验过优易办公金牌维保服务,还在犹豫什么!金牌服务从点击开始!</p>
-      <el-button type="danger" plain>加入金牌维保服务</el-button>
+      <el-button type="danger" plain @click="handleApply">加入金牌维保服务</el-button>
     </div>
   </div>
 </template>
 
 <script setup lang="ts">
 import { PageTitle } from '@/components';
+import { useRouter } from 'vue-router';
+
+const router = useRouter();
+
+const handleApply = () => {
+  router.push(`/valueAdded/maintenanceApply`);
+};
 </script>
 
 <style scoped lang="scss">