Browse Source

服务工单完成

aiwenzhu 1 month ago
parent
commit
83c20954fa

+ 35 - 35
src/main.js

@@ -1,27 +1,27 @@
-import Vue from 'vue'
-import VueRouter from 'vue-router'
-import 'normalize.css/normalize.css' // A modern alternative to CSS resets
+import Vue from "vue";
+import VueRouter from "vue-router";
+import "normalize.css/normalize.css"; // A modern alternative to CSS resets
 
-import ElementUI from 'element-ui'
-import 'element-ui/lib/theme-chalk/index.css'
-import locale from 'element-ui/lib/locale/lang/zh-CN' // lang i18n
-import iconfont from './assets/iconfont/iconfont.css'
+import ElementUI from "element-ui";
+import "element-ui/lib/theme-chalk/index.css";
+import locale from "element-ui/lib/locale/lang/zh-CN"; // lang i18n
+import iconfont from "./assets/iconfont/iconfont.css";
 
-import '@/styles/index.scss' // global css
+import "@/styles/index.scss"; // global css
 
-import * as Sentry from '@sentry/browser'
-import { Vue as VueIntegration } from '@sentry/integrations'
+import * as Sentry from "@sentry/browser";
+import { Vue as VueIntegration } from "@sentry/integrations";
 
-import App from './App'
-import store from './store'
-import router from './router'
-import i18n from './i18n'
-import '@/icons' // icon
-import '@/permission' // permission control
-import './icons'
-import * as filters from './filters' // global filters
+import App from "./App";
+import store from "./store";
+import router from "./router";
+import i18n from "./i18n";
+import "@/icons"; // icon
+import "@/permission"; // permission control
+import "./icons";
+import * as filters from "./filters"; // global filters
 
-Vue.config.productionTip = false
+Vue.config.productionTip = false;
 /**
  * If you don't want to use mock-server
  * you want to use MockJs for mock api
@@ -31,33 +31,33 @@ Vue.config.productionTip = false
  * please remove it before going online! ! !
  */
 // import { mockXHR } from '../mock'
-if (process.env.NODE_ENV === 'production') {
+if (process.env.NODE_ENV === "production") {
   // mockXHR()
 }
 
 // console.log(process.env)
-if (process.env.NODE_ENV !== 'development') {
+if (process.env.NODE_ENV !== "development") {
   Sentry.init({
-    release: 'vislib@' + process.env.npm_package_version,
-    dsn: 'https://9d8ee0ea1a2749949dd1e641b0f7c071@o286322.ingest.sentry.io/5217806',
-    integrations: [new VueIntegration({ Vue, attachProps: true })]
-  })
+    release: "vislib@" + process.env.npm_package_version,
+    dsn: "https://9d8ee0ea1a2749949dd1e641b0f7c071@o286322.ingest.sentry.io/5217806",
+    integrations: [new VueIntegration({ Vue, attachProps: true })],
+  });
 }
-store.commit('app/SET_LANG', 'CN')
+store.commit("app/SET_LANG", "CN");
 // Vue.use(ElementUI, { locale })
-Vue.use(VueRouter)
+Vue.use(VueRouter);
 Vue.use(ElementUI, {
-  i18n: (key, value) => i18n.t(key, value)
-})
+  i18n: (key, value) => i18n.t(key, value),
+});
 // register global utility filters
-Object.keys(filters).forEach(key => {
-  Vue.filter(key, filters[key])
-})
+Object.keys(filters).forEach((key) => {
+  Vue.filter(key, filters[key]);
+});
 
 new Vue({
-  el: '#app',
+  el: "#app",
   router,
   store,
   i18n,
-  render: h => h(App)
-})
+  render: (h) => h(App),
+});

+ 29 - 3
src/views/productManagement/installationOrder/components/AddDialog.vue

@@ -525,7 +525,7 @@ export default {
           goodsImagePath: selectedProduct.goodsImagePath || "",
           goodsUnit: selectedProduct.goodsUnit,
           stock: selectedProduct.stock || 0,
-          orderQuantity: 1,
+          orderQuantity: "",
           shippedQuantity: 0,
           unshippedQuantity: 0,
           remark: "",
@@ -539,10 +539,10 @@ export default {
       // 只允许输入正整数
       value = value.replace(/[^\d]/g, "");
       if (value === "") {
-        row.orderQuantity = 1;
+        row.orderQuantity = "";
       } else {
         const num = parseInt(value);
-        row.orderQuantity = num || 1;
+        row.orderQuantity = num || "";
       }
     },
 
@@ -557,6 +557,16 @@ export default {
               return;
             }
 
+            // 检查每个货品的订单数量是否大于0
+            const invalidProducts = this.form.products.filter(
+              (item) => !item.orderQuantity || parseInt(item.orderQuantity) <= 0
+            );
+
+            if (invalidProducts.length > 0) {
+              this.$message.warning("所有货品的订单数量必须大于0");
+              return;
+            }
+
             // 构建保存参数
             const params = {
               common: {
@@ -643,6 +653,22 @@ export default {
                     },
                   ],
                 },
+                {
+                  name: "insertInstallationOrderProcessLog",
+                  type: "e",
+                  parammaps: {
+                    orderId: "@insertInstallationOrder.LastInsertId",
+                    operationType: "create",
+                    operationUserId: this.form.orderer,
+                    operationUserName:
+                      this.installerOptions.find(
+                        (item) => item.value === this.form.orderer
+                      )?.label || "",
+                    beforeStatus: "未接单",
+                    afterStatus: "未接单",
+                    operationContent: "创建服务工单",
+                  },
+                },
               ],
             };
 

+ 164 - 124
src/views/productManagement/installationOrder/components/CheckDialog.vue

@@ -3,12 +3,19 @@
   <el-dialog
     title="验收"
     :visible.sync="visible"
-    width="500px"
+    width="650px"
     :close-on-click-modal="false"
     :close-on-press-escape="false"
     @close="handleDialogClose"
     custom-class="check-dialog"
   >
+    <!-- 添加隐藏的预览组件 -->
+    <el-image
+      style="display: none"
+      ref="previewImage"
+      :src="previewUrl"
+      :preview-src-list="previewSrcList"
+    />
     <el-form ref="form" :model="form" :rules="rules" label-width="120px">
       <el-form-item label="服务人员" prop="serviceStaffId">
         <el-select
@@ -26,22 +33,39 @@
         </el-select>
       </el-form-item>
 
-      <el-form-item label="客户联系人" prop="customerContactId">
+      <el-form-item label="客户联系人" prop="customContact">
         <el-select
-          v-model="form.customerContactId"
-          placeholder="请选择客户联系人"
+          v-model="form.customContact"
+          placeholder="请选择或输入联系人"
           style="width: 100%"
           filterable
+          allow-create
+          default-first-option
+          clearable
+          @change="handleContactChange"
         >
           <el-option
             v-for="item in customerContactOptions"
             :key="item.id"
             :label="item.contactName"
             :value="item.id"
-          />
+          >
+            <span>{{ item.contactName }}</span>
+            <span style="float: right; color: #8492a6; font-size: 13px">
+              {{ item.telephone || "无电话" }}
+            </span>
+          </el-option>
         </el-select>
       </el-form-item>
 
+      <el-form-item label="联系人电话" prop="telephone">
+        <el-input
+          v-model="form.telephone"
+          placeholder="请输入联系人电话"
+          style="width: 100%"
+        />
+      </el-form-item>
+
       <el-form-item label="验收日期" prop="checkDate">
         <el-date-picker
           v-model="form.checkDate"
@@ -52,47 +76,30 @@
       </el-form-item>
 
       <el-form-item label="验收单" prop="checkImage">
-        <el-upload
-          id="uploadPic"
-          list-type="picture-card"
-          class="uploadPic"
-          :action="uploadImageUrl"
-          :limit="1"
-          :headers="headers"
-          :file-list="fileList"
-          :on-preview="handlePicPreview"
-          :auto-upload="true"
-          :on-change="
-            (file, fileList) => {
-              return handlePicChange(file, fileList);
-            }
-          "
-          :on-success="
-            (response, file, fileList) => {
-              return handlePicSuccess(response, file, fileList);
-            }
-          "
-          :on-remove="
-            (file, fileList) => {
-              return handlePicRemove(file, fileList);
-            }
-          "
-        >
-          <i slot="default" class="el-icon-plus"></i>
-        </el-upload>
+        <div class="upload-wrapper">
+          <el-upload
+            ref="upload"
+            id="uploadPic"
+            list-type="picture-card"
+            class="uploadPic"
+            :action="uploadImageUrl"
+            :limit="3"
+            :headers="headers"
+            :file-list="fileList"
+            :on-preview="handlePicPreview"
+            :auto-upload="true"
+            :on-change="handlePicChange"
+            :on-success="handlePicSuccess"
+            :on-remove="handlePicRemove"
+            :on-exceed="handleExceed"
+          >
+            <i class="el-icon-plus"></i>
+          </el-upload>
+          <div class="upload-tip">最多上传3张图片</div>
+        </div>
       </el-form-item>
     </el-form>
 
-    <!-- 图片预览 -->
-    <el-dialog
-      title="预览"
-      :visible.sync="previewVisible"
-      append-to-body
-      width="800px"
-    >
-      <img :src="form.checkImagePreview" style="width: 100%" />
-    </el-dialog>
-
     <div slot="footer" class="dialog-footer">
       <el-button
         type="primary"
@@ -108,6 +115,7 @@
 
 <script>
 import { getToken } from "@/utils/auth";
+
 export default {
   name: "CheckDialog",
   props: {
@@ -135,19 +143,22 @@ export default {
   data() {
     return {
       submitting: false,
-      previewVisible: false,
       form: {
         serviceStaffId: "",
-        customerContactId: "",
+        customContact: "",
+        telephone: "",
         checkDate: new Date(),
-        checkImage: "",
+        checkImage: [],
       },
       rules: {
         serviceStaffId: [
           { required: true, message: "请选择服务人员", trigger: "change" },
         ],
-        customerContactId: [
-          { required: true, message: "请选择客户联系人", trigger: "change" },
+        customContact: [
+          { required: true, message: "请输入联系人", trigger: "blur" },
+        ],
+        telephone: [
+          { required: true, message: "请输入联系人电话", trigger: "blur" },
         ],
         checkDate: [
           { required: true, message: "请选择验收日期", trigger: "change" },
@@ -163,6 +174,8 @@ export default {
         token: getToken(),
       },
       fileList: [],
+      previewUrl: "",
+      previewSrcList: [],
     };
   },
   watch: {
@@ -176,11 +189,12 @@ export default {
     initForm() {
       this.form = {
         serviceStaffId: parseInt(this.currentUser.id),
+        customContact: "",
+        telephone: "",
         checkDate: new Date(),
-        checkImage: "",
+        checkImage: [],
       };
       this.fileList = [];
-      console.log("this.form", this.form);
     },
     beforeUpload(file) {
       const isImage = file.type.startsWith("image/");
@@ -198,7 +212,9 @@ export default {
     },
     handlePicSuccess(response, file, fileList) {
       const id = response.execresult.LastInsertId;
-      this.form.checkImage = id;
+      // 直接将新ID添加到数组中
+      this.form.checkImage.push(id);
+
       if (file.raw) {
         file.url = window.URL.createObjectURL(file.raw);
       }
@@ -206,25 +222,73 @@ export default {
     },
     handlePicChange(file, fileList) {
       this.fileList = fileList;
+      // 根据文件列表长度控制上传按钮显示
+      const uploadBtn = this.$refs.upload.$el.querySelector(
+        ".el-upload--picture-card"
+      );
+      if (uploadBtn) {
+        uploadBtn.style.display =
+          fileList.length >= 3 ? "none" : "inline-block";
+      }
     },
     handlePicPreview(file) {
-      this.previewVisible = true;
+      const { url } = file;
+      if (!url) return;
+
+      this.previewUrl = url;
+      this.previewSrcList = [url];
+
+      // 手动触发预览
+      const previewImage = this.$refs.previewImage;
+      if (previewImage) {
+        previewImage.showViewer = true;
+      }
     },
     handlePicRemove(file, fileList) {
       this.fileList = fileList;
+      // 从数组中移除被删除图片的ID
+      if (file.response && file.response.execresult) {
+        const removedId = file.response.execresult.LastInsertId;
+        this.form.checkImage = this.form.checkImage.filter(
+          (id) => id !== removedId
+        );
+      }
+
       if (fileList.length === 0) {
-        this.form.checkImage = "";
+        this.form.checkImage = [];
+      }
+
+      // 删除后重新显示上传按钮
+      const uploadBtn = this.$refs.upload.$el.querySelector(
+        ".el-upload--picture-card"
+      );
+      if (uploadBtn) {
+        uploadBtn.style.display = "inline-block";
       }
     },
+    handleExceed(files, fileList) {
+      this.$message.warning("最多只能上传3张图片");
+    },
     handleSubmit() {
+      const isNewContact = !this.customerContactOptions.some(
+        (item) => item.id === this.form.customContact
+      );
+
       this.$refs.form.validate(async (valid) => {
         if (valid) {
           this.submitting = true;
           try {
+            // 检查是否是新增的联系人
+            const isNewContact = !this.customerContactOptions.some(
+              (item) => item.id === this.form.customContact
+            );
+
             await this.$emit("confirm", {
+              isNewContact: isNewContact,
               orderId: this.rowData.id,
               serviceStaffId: this.form.serviceStaffId,
-              customerContactId: this.form.customerContactId,
+              customContact: this.form.customContact,
+              telephone: this.form.telephone,
               checkDate: this.formatDate(this.form.checkDate),
               checkImage: this.form.checkImage,
             });
@@ -249,90 +313,49 @@ export default {
       const day = String(d.getDate()).padStart(2, "0");
       return `${year}-${month}-${day}`;
     },
+    handleContactChange(value) {
+      if (value) {
+        const selectedContact = this.customerContactOptions.find(
+          (item) => item.id === value
+        );
+        if (selectedContact) {
+          this.form.telephone = selectedContact.telephone || "";
+        }
+      } else {
+        this.form.telephone = "";
+      }
+    },
   },
 };
 </script>
 
-<style lang="scss" scoped>
+<style lang="scss">
 .check-dialog {
   :deep(.el-dialog__body) {
     padding: 20px 20px 10px 20px;
   }
 
-  .upload-container {
-    width: 100%;
-
-    .upload-area {
-      width: 100%;
-      height: 148px;
-      border: 1px dashed #d9d9d9;
-      border-radius: 6px;
-      cursor: pointer;
-      position: relative;
-      overflow: hidden;
-      display: flex;
-      flex-direction: column;
-      justify-content: center;
-      align-items: center;
-
-      &:hover {
-        border-color: #409eff;
-      }
-
-      .el-icon-plus {
-        font-size: 28px;
-        color: #8c939d;
-        margin-bottom: 8px;
+  .upload-wrapper {
+    .uploadPic {
+      :deep(.el-upload--picture-card) {
+        width: 148px !important;
+        height: 148px !important;
+        line-height: 148px !important;
+        margin: 0 8px 8px 0;
+        transition: display 0.3s;
       }
 
-      .upload-text {
-        font-size: 14px;
-        color: #606266;
+      :deep(.el-upload-list--picture-card .el-upload-list__item) {
+        width: 148px !important;
+        height: 148px !important;
+        margin: 0 8px 8px 0;
       }
     }
 
-    .image-preview {
-      width: 100%;
-      height: 148px;
-      position: relative;
-      border: 1px solid #d9d9d9;
-      border-radius: 6px;
-      overflow: hidden;
-
-      .preview-img {
-        width: 100%;
-        height: 100%;
-        object-fit: cover;
-      }
-
-      .image-actions {
-        position: absolute;
-        top: 0;
-        left: 0;
-        width: 100%;
-        height: 100%;
-        background: rgba(0, 0, 0, 0.5);
-        display: flex;
-        justify-content: center;
-        align-items: center;
-        opacity: 0;
-        transition: opacity 0.3s;
-
-        i {
-          color: #fff;
-          font-size: 20px;
-          margin: 0 10px;
-          cursor: pointer;
-
-          &:hover {
-            transform: scale(1.1);
-          }
-        }
-      }
-
-      &:hover .image-actions {
-        opacity: 1;
-      }
+    .upload-tip {
+      font-size: 12px;
+      color: #909399;
+      margin-top: 5px;
     }
   }
 
@@ -377,4 +400,21 @@ export default {
     }
   }
 }
+
+// 添加全局样式以优化遮罩层
+:deep(.v-modal) {
+  opacity: 0.7 !important;
+}
+
+// 优化预览样式
+.el-image-viewer__wrapper {
+  .el-image-viewer__btn {
+    color: #fff;
+
+    i {
+      color: #fff;
+      font-size: 24px;
+    }
+  }
+}
 </style>

+ 20 - 0
src/views/productManagement/installationOrder/components/DispatchDialog.vue

@@ -157,6 +157,26 @@ export default {
                     dispatcherDate: this.formatDate(this.form.dispatcherDate),
                   },
                 },
+                {
+                  name: "insertInstallationOrderProcessLog",
+                  type: "e",
+                  parammaps: {
+                    orderId: this.rowData.id,
+                    operationType: "dispatch",
+                    operationUserId: this.currentUser.id,
+                    operationUserName: this.currentUser.name,
+                    beforeStatus: this.rowData.statusName,
+                    afterStatus: this.rowData.statusName,
+                    operationContent:
+                      `派单给服务人员:` +
+                      this.installerOptions
+                        .filter((item) =>
+                          this.form.serviceStaffIds.includes(item.value)
+                        )
+                        .map((item) => item.label)
+                        .join(","),
+                  },
+                },
               ],
             };
 

+ 13 - 0
src/views/productManagement/installationOrder/components/EditDialog.vue

@@ -696,6 +696,19 @@ export default {
                     },
                   ],
                 },
+                {
+                  name: "insertInstallationOrderProcessLog",
+                  type: "e",
+                  parammaps: {
+                    orderId: this.rowData.id,
+                    operationType: "edit",
+                    operationUserId: this.currentUser.id,
+                    operationUserName: this.currentUser.name,
+                    beforeStatus: this.rowData.statusName,
+                    afterStatus: "未接单",
+                    operationContent: "编辑服务工单",
+                  },
+                },
               ],
             };
 

File diff suppressed because it is too large
+ 563 - 353
src/views/productManagement/installationOrder/components/ViewDialog.vue


+ 282 - 70
src/views/productManagement/installationOrder/index.vue

@@ -8,6 +8,33 @@
       :inline="true"
       class="demo-form-inline"
     >
+      <el-form-item>
+        <el-input
+          v-model="queryParams.orderNo"
+          placeholder="服务单号"
+          clearable
+          size="small"
+          style="width: 150px"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-input
+          v-model="queryParams.contractNo"
+          placeholder="合同单号"
+          clearable
+          size="small"
+          style="width: 150px"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-input
+          v-model="queryParams.deliveryNo"
+          placeholder="发货单号"
+          clearable
+          size="small"
+          style="width: 150px"
+        />
+      </el-form-item>
       <el-form-item>
         <el-select
           v-model="queryParams.serviceStaffIds"
@@ -90,6 +117,7 @@
           style="width: 250px"
         />
       </el-form-item>
+      <div style="width: 100%"></div>
       <el-form-item>
         <el-button
           size="small"
@@ -119,8 +147,8 @@
           style="margin-left: 8px"
         >
           <el-radio-button label="">全部</el-radio-button>
-          <el-radio-button label="待处理"
-            >待处理<el-badge
+          <el-radio-button label="未接单"
+            >未接单<el-badge
               v-if="pendingCount > 0"
               :value="pendingCount"
               class="pending-badge"
@@ -223,7 +251,9 @@
                         scope.row.progress > 0
                           ? scope.row.progress + '%'
                           : '2px',
-                      backgroundColor: getProgressBarColor(scope.row.progress),
+                      backgroundColor: getProgressBarColor(
+                        scope.row.statusName
+                      ),
                     }"
                   >
                     <span class="progress-text" v-if="scope.row.progress > 0">
@@ -354,45 +384,91 @@
         <template slot-scope="scope">
           <el-button
             size="mini"
-            type="info"
-            plain
+            style="
+              background-color: #2d8cf0;
+              border-color: #2d8cf0;
+              color: #fff;
+            "
             @click="handleView(scope.row)"
             >查看</el-button
           >
-          <el-button size="mini" type="primary" @click="handleEdit(scope.row)"
-            >编辑</el-button
-          >
-          <el-dropdown
-            trigger="click"
-            @command="(command) => handleCommand(command, scope.row)"
-          >
-            <el-button size="mini" type="success">
-              更多<i class="el-icon-arrow-down el-icon--right"></i>
-            </el-button>
-            <el-dropdown-menu slot="dropdown">
-              <el-dropdown-item command="delete" class="dropdown-danger"
-                >删除</el-dropdown-item
-              >
-              <el-dropdown-item command="dispatch" class="dropdown-success"
-                >派单</el-dropdown-item
-              >
-              <el-dropdown-item command="reject" class="dropdown-danger"
-                >驳回</el-dropdown-item
-              >
-              <el-dropdown-item command="dailyWrite" class="dropdown-info"
-                >填写</el-dropdown-item
-              >
-              <el-dropdown-item command="complete" class="dropdown-success"
-                >完成</el-dropdown-item
-              >
-              <el-dropdown-item command="accept" class="dropdown-primary"
-                >接单</el-dropdown-item
-              >
-              <el-dropdown-item command="check" class="dropdown-warning"
-                >验收</el-dropdown-item
-              >
-            </el-dropdown-menu>
-          </el-dropdown>
+          <template v-if="scope.row.statusName === '未接单'">
+            <el-button size="mini" type="primary" @click="handleEdit(scope.row)"
+              >编辑</el-button
+            >
+            <el-button
+              size="mini"
+              type="success"
+              @click="handleDispatch(scope.row)"
+              >派单</el-button
+            >
+            <el-button
+              size="mini"
+              style="
+                background-color: #e74c3c;
+                border-color: #e74c3c;
+                color: #fff;
+              "
+              @click="handleReject(scope.row)"
+              >驳回</el-button
+            >
+            <el-button
+              size="mini"
+              style="
+                background-color: #f39c12;
+                border-color: #f39c12;
+                color: #fff;
+              "
+              @click="handleAccept(scope.row)"
+              >接单</el-button
+            >
+            <el-button
+              size="mini"
+              type="danger"
+              @click="handleDelete(scope.row)"
+              >删除</el-button
+            >
+          </template>
+          <template v-else-if="scope.row.statusName === '处理中'">
+            <el-button
+              size="mini"
+              style="
+                background-color: #409eff;
+                border-color: #409eff;
+                color: #fff;
+              "
+              @click="handleDailyWrite(scope.row)"
+              >填写</el-button
+            >
+            <el-button
+              size="mini"
+              type="success"
+              @click="handleComplete(scope.row)"
+              >完成</el-button
+            >
+          </template>
+          <template v-else-if="scope.row.statusName === '已完成未验收'">
+            <el-button
+              size="mini"
+              type="warning"
+              @click="handleCheck(scope.row)"
+              >验收</el-button
+            >
+          </template>
+          <template v-else-if="scope.row.statusName === '已完成'">
+            <!-- 仅保留查看按钮 -->
+          </template>
+          <template v-else-if="scope.row.statusName === '接单驳回'">
+            <el-button size="mini" type="primary" @click="handleEdit(scope.row)"
+              >编辑</el-button
+            >
+            <el-button
+              size="mini"
+              type="danger"
+              @click="handleDelete(scope.row)"
+              >删除</el-button
+            >
+          </template>
         </template>
       </el-table-column>
     </el-table>
@@ -540,14 +616,17 @@ export default {
         orderTimeRange: [],
         completeTimeRange: [],
         viewType: "",
+        orderNo: "",
+        deliveryNo: "",
+        contractNo: "",
       },
       // 状态选项
       statusOptions: [
-        { label: "待处理", value: "待处理" },
+        { label: "未接单", value: "未接单" },
         { label: "处理中", value: "处理中" },
         { label: "已完成未验收", value: "已完成未验收" },
         { label: "已完成", value: "已完成" },
-        { label: "已驳回", value: "已驳回" },
+        { label: "接单驳回", value: "接单驳回" },
       ],
       // 项目选项
       projectOptions: [],
@@ -724,15 +803,24 @@ export default {
     },
     handleViewTypeChange(value) {
       this.queryParams.statusName = value;
+      this.queryParams.serviceStaffIds = "";
+      this.queryParams.customerName = "";
+      this.queryParams.projectName = "";
+      this.queryParams.projectId = "";
+      this.queryParams.orderTimeRange = [];
+      this.queryParams.completeTimeRange = [];
+      this.queryParams.orderNo = "";
+      this.queryParams.deliveryNo = "";
+      this.queryParams.contractNo = "";
       this.getList();
     },
     getStatusType(statusName) {
       const statusMap = {
-        待处理: "info",
+        未接单: "info",
         处理中: "primary",
         已完成: "success",
         已完成未验收: "warning",
-        已驳回: "danger",
+        接单驳回: "danger",
       };
       return statusMap[statusName] || "info";
     },
@@ -762,6 +850,19 @@ export default {
               parammaps: {
                 orderId: row.id,
               },
+            }, // 在 handleDelete 方法的 params.data 数组中添加:
+            {
+              name: "insertInstallationOrderProcessLog",
+              type: "e",
+              parammaps: {
+                orderId: row.id,
+                operationType: "delete",
+                operationUserId: this.currentUser.id,
+                operationUserName: this.currentUser.name,
+                beforeStatus: row.statusName,
+                afterStatus: row.statusName,
+                operationContent: "删除服务工单",
+              },
             },
           ],
         };
@@ -822,7 +923,7 @@ export default {
                   goodsName: item.goodsName,
                   goodsId: item.goodsId,
                   todayQuantity: item.todayQuantity || 0,
-                  remark: item.remark,
+                  remark: item.installRemark,
                 })),
               },
               children: [
@@ -856,6 +957,26 @@ export default {
               parammaps: {
                 orderId: this.currentRow.id,
               },
+            }, // 在 handleDailyWriteConfirm 方法的 params.data 数组中添加:
+            {
+              name: "insertInstallationOrderProcessLog",
+              type: "e",
+              parammaps: {
+                orderId: this.currentRow.id,
+                operationType: "write",
+                operationUserId: this.currentUser.id,
+                operationUserName: this.currentUser.name,
+                beforeStatus: this.currentRow.statusName,
+                afterStatus: this.currentRow.statusName,
+                operationContent: `填写日期:${formData.dates.join(
+                  ","
+                )}|产品名称:${formData.records
+                  .map((item) => item.goodsName)
+                  .join(",")}|数量:${formData.records.reduce(
+                  (sum, item) => sum + (parseInt(item.todayQuantity) || 0),
+                  0
+                )}`,
+              },
             },
           ],
         };
@@ -890,6 +1011,19 @@ export default {
               parammaps: {
                 orderId: row.id,
               },
+            }, // 在 handleComplete 方法的 params.data 数组中添加:
+            {
+              name: "insertInstallationOrderProcessLog",
+              type: "e",
+              parammaps: {
+                orderId: row.id,
+                operationType: "complete",
+                operationUserId: this.currentUser.id,
+                operationUserName: this.currentUser.name,
+                beforeStatus: row.statusName,
+                afterStatus: "已完成未验收",
+                operationContent: "完成服务工单",
+              },
             },
           ],
         };
@@ -906,7 +1040,7 @@ export default {
         this.$message.error("操作失败");
       }
     },
-    // 验收
+    // 验收-获取客户联系人
     async handleCheck(row) {
       this.currentRow = row;
       this.checkDialogVisible = true;
@@ -935,28 +1069,74 @@ export default {
           common: {
             returnmap: "0",
           },
-          data: [
-            {
-              name: "checkInstallationOrder",
-              type: "e",
-              parammaps: {
-                orderId: formData.orderId,
-                checkUserId: formData.serviceStaffId,
-                checkUserName:
-                  this.installerOptions.find(
-                    (item) => item.id === formData.serviceStaffId
-                  )?.label || "",
-                contactId: formData.customerContactId,
-                contactName:
-                  this.customerContactOptions.find(
-                    (item) => item.id === formData.customerContactId
-                  )?.name || "",
-                checkDate: formData.checkDate,
-                contactImageId: formData.checkImage,
+          data: [],
+        };
+        if (formData.isNewContact) {
+          params.data.push({
+            name: "insertContacts",
+            type: "e",
+            parammaps: {
+              customerId: this.currentRow.customerId,
+              contactName: formData.customContact,
+              telephone: formData.telephone,
+            },
+          });
+        }
+        // 处理验收图片
+        if (formData.checkImage.length > 0) {
+          params.data.push({
+            name: "insertInstallationOrderImages",
+            resultmaps: {
+              list: formData.checkImage.map((item, index) => ({
+                imageId: item,
+                imageType: "check_image",
+              })),
+            },
+            children: [
+              {
+                name: "insertInstallationOrderImages",
+                type: "e",
+                parammaps: {
+                  orderId: this.currentRow.id,
+                  imageId: "@insertInstallationOrderImages.imageId",
+                  imageType: "@insertInstallationOrderImages.imageType",
+                },
               },
+            ],
+          });
+        }
+
+        params.data.push(
+          {
+            name: "checkInstallationOrder",
+            type: "e",
+            parammaps: {
+              orderId: formData.orderId,
+              checkUserId: formData.serviceStaffId,
+              checkUserName:
+                this.installerOptions.find(
+                  (item) => item.id === formData.serviceStaffId
+                )?.label || "",
+              contactId: formData.isNewContact
+                ? "@insertContacts.LastInsertId"
+                : formData.customContact,
+              checkDate: formData.checkDate,
             },
-          ],
-        };
+          },
+          {
+            name: "insertInstallationOrderProcessLog",
+            type: "e",
+            parammaps: {
+              orderId: this.currentRow.id,
+              operationType: "check",
+              operationUserId: this.currentUser.id,
+              operationUserName: this.currentUser.name,
+              beforeStatus: this.currentRow.statusName,
+              afterStatus: "已完成",
+              operationContent: "验收服务工单",
+            },
+          }
+        );
 
         const response = await ExecDataByConfig(params);
         if (response.msg === "ok") {
@@ -1010,11 +1190,17 @@ export default {
     handleEditSuccess() {
       this.getList(); // 刷新列表数据
     },
-    getProgressBarColor(progress) {
-      if (progress >= 100) return "#67C23A"; // 完成
-      if (progress >= 80) return "#409EFF"; // 接近完成
-      if (progress >= 50) return "#E6A23C"; // 过半
-      return "#909399"; // 开始阶段
+    getProgressBarColor(status) {
+      // 根据状态返回对应的颜色
+      const statusColorMap = {
+        未接单: "#909399", // 灰色
+        处理中: "#409eff", // 蓝色
+        已完成未验收: "#e6a23c", // 橙色
+        已完成: "#67c23a", // 绿色
+        接单驳回: "#f56c6c", // 红色
+      };
+
+      return statusColorMap[status] || "#909399";
     },
     showProgressTooltip(event, percentage) {
       this.progressTooltip = document.createElement("div");
@@ -1067,6 +1253,19 @@ export default {
                 rejectReason: formData.rejectReason,
               },
             },
+            {
+              name: "insertInstallationOrderProcessLog",
+              type: "e",
+              parammaps: {
+                orderId: this.currentRow.id,
+                operationType: "reject",
+                operationUserId: this.currentUser.id,
+                operationUserName: this.currentUser.name,
+                beforeStatus: this.currentRow.statusName,
+                afterStatus: "接单驳回",
+                operationContent: `驳回原因:${formData.rejectReason}`,
+              },
+            },
           ],
         };
 
@@ -1100,6 +1299,19 @@ export default {
                 acceptName: this.currentUser.name,
               },
             },
+            {
+              name: "insertInstallationOrderProcessLog",
+              type: "e",
+              parammaps: {
+                orderId: this.currentRow.id,
+                operationType: "accept",
+                operationUserId: this.currentUser.id,
+                operationUserName: this.currentUser.name,
+                beforeStatus: this.currentRow.statusName,
+                afterStatus: "处理中",
+                operationContent: "接单处理",
+              },
+            },
           ],
         };
 

+ 66 - 40
src/views/productManagement/installationOrder/mixins/paramsMixin.js

@@ -6,27 +6,37 @@ export default {
      * @param {string} name - 接口名称
      * @returns {Object} 处理后的参数
      */
-    handleParams(queryParams, name = 'getInstallationOrderList') {
-      const { orderTimeRange, completeTimeRange, ...restParams } = queryParams
-      
+    handleParams(queryParams, name = "getInstallationOrderList") {
+      const { orderTimeRange, completeTimeRange, ...restParams } = queryParams;
+
       return {
         name,
         page: queryParams.pageNum,
         offset: queryParams.pageNum,
         pagecount: queryParams.pageSize,
-        returntype: 'Map',
+        returntype: "Map",
         parammaps: {
-          serviceStaffIds: restParams.serviceStaffIds || '',
-          customerName: restParams.customerName || '',
-          statusName: restParams.statusName || '',
-          projectId: restParams.projectId || '',
-          orderNo: restParams.orderNo || '',
-          orderStartTime: orderTimeRange?.[0] ? this.formatDate(orderTimeRange[0]) : '',
-          orderEndTime: orderTimeRange?.[1] ? this.formatDate(orderTimeRange[1]) : '',
-          completeStartTime: completeTimeRange?.[0] ? this.formatDate(completeTimeRange[0]) : '',
-          completeEndTime: completeTimeRange?.[1] ? this.formatDate(completeTimeRange[1]) : ''
-        }
-      }
+          serviceStaffIds: restParams.serviceStaffIds || "",
+          customerName: restParams.customerName || "",
+          statusName: restParams.statusName || "",
+          projectId: restParams.projectId || "",
+          orderNo: restParams.orderNo || "",
+          orderStartTime: orderTimeRange?.[0]
+            ? this.formatDate(orderTimeRange[0])
+            : "",
+          orderEndTime: orderTimeRange?.[1]
+            ? this.formatDate(orderTimeRange[1])
+            : "",
+          completeStartTime: completeTimeRange?.[0]
+            ? this.formatDate(completeTimeRange[0])
+            : "",
+          completeEndTime: completeTimeRange?.[1]
+            ? this.formatDate(completeTimeRange[1])
+            : "",
+          contractNo: restParams.contractNo || "",
+          deliveryNo: restParams.deliveryNo || "",
+        },
+      };
     },
 
     /**
@@ -37,26 +47,42 @@ export default {
      */
     handleExportParams(queryParams, name) {
       if (!name) {
-        throw new Error('导出接口名称不能为空')
+        throw new Error("导出接口名称不能为空");
       }
-      
-      const { orderTimeRange, completeTimeRange, pageNum, pageSize, ...restParams } = queryParams
-      
+
+      const {
+        orderTimeRange,
+        completeTimeRange,
+        pageNum,
+        pageSize,
+        ...restParams
+      } = queryParams;
+
       return {
         name,
-        returntype: 'Map',
+        returntype: "Map",
         parammaps: {
-          serviceStaffIds: restParams.serviceStaffIds || '',
-          customerName: restParams.customerName || '',
-          statusName: restParams.statusName || '',
-          projectId: restParams.projectId || '',
-          orderNo: restParams.orderNo || '',
-          orderStartTime: orderTimeRange?.[0] ? this.formatDate(orderTimeRange[0]) : '',
-          orderEndTime: orderTimeRange?.[1] ? this.formatDate(orderTimeRange[1]) : '',
-          completeStartTime: completeTimeRange?.[0] ? this.formatDate(completeTimeRange[0]) : '',
-          completeEndTime: completeTimeRange?.[1] ? this.formatDate(completeTimeRange[1]) : ''
-        }
-      }
+          serviceStaffIds: restParams.serviceStaffIds || "",
+          customerName: restParams.customerName || "",
+          statusName: restParams.statusName || "",
+          projectId: restParams.projectId || "",
+          orderNo: restParams.orderNo || "",
+          orderStartTime: orderTimeRange?.[0]
+            ? this.formatDate(orderTimeRange[0])
+            : "",
+          orderEndTime: orderTimeRange?.[1]
+            ? this.formatDate(orderTimeRange[1])
+            : "",
+          completeStartTime: completeTimeRange?.[0]
+            ? this.formatDate(completeTimeRange[0])
+            : "",
+          completeEndTime: completeTimeRange?.[1]
+            ? this.formatDate(completeTimeRange[1])
+            : "",
+          contractNo: restParams.contractNo || "",
+          deliveryNo: restParams.deliveryNo || "",
+        },
+      };
     },
 
     /**
@@ -65,12 +91,12 @@ export default {
      * @returns {string} 格式化后的日期字符串
      */
     formatDate(date) {
-      if (!date) return ''
-      const d = new Date(date)
-      const year = d.getFullYear()
-      const month = String(d.getMonth() + 1).padStart(2, '0')
-      const day = String(d.getDate()).padStart(2, '0')
-      return `${year}-${month}-${day}`
-    }
-  }
-} 
+      if (!date) return "";
+      const d = new Date(date);
+      const year = d.getFullYear();
+      const month = String(d.getMonth() + 1).padStart(2, "0");
+      const day = String(d.getDate()).padStart(2, "0");
+      return `${year}-${month}-${day}`;
+    },
+  },
+};

+ 110 - 0
功能更新说明

@@ -0,0 +1,110 @@
+1. UI优化
+   - 将el-autocomplete改为el-select组件
+   - 添加了allow-create属性支持创建新选项
+   - 在下拉选项中显示联系人电话
+   - 统一了与服务人员选择框的样式
+
+2. 交互优化
+   - 支持搜索过滤(filterable)
+   - 支持直接输入新联系人(allow-create)
+   - 选择已有联系人时自动填充电话
+   - 清空选择时重置电话
+
+3. 数据处理逻辑
+   - 区分新增和选择已有联系人
+   - 新增联系人时先调用保存接口
+   - 保存成功后再提交验收信息
+   - 保存失败时提示错误并中断提交
+
+4. 新增功能
+   - 添加saveNewCustomerContact方法处理新联系人保存
+   - 优化handleContactChange方法处理选择变更
+
+5. 数据结构简化
+   - 移除了customerContactId字段
+   - 只保留customContactName和telephone字段
+   - 简化了表单验证规则
+
+6. 验证规则优化
+   - 客户联系人改为非必填
+   - 联系人姓名设为必填
+   - 电话号码添加了格式验证(11位手机号)
+
+7. 数据提交更新
+   - 提交时增加了customContactName和telephone字段
+
+8. 上传组件优化
+   - 设置最大上传数量为3张
+   - 添加上传限制提示文字
+   - 简化上传相关事件绑定
+
+9. 数据结构优化
+   - 新增checkImageList数组存储图片ID
+   - 使用逗号拼接方式存储多个图片ID
+   - 添加previewUrl用于图片预览
+
+10. 功能增强
+    - 添加超出限制提示
+    - 优化图片预览功能
+    - 完善图片删除逻辑
+
+11. 数据处理逻辑
+    - 上传成功时添加ID到列表并更新拼接字符串
+    - 删除图片时从列表移除并更新拼接字符串
+    - 表单重置时清空图片列表
+
+1. 上传方式优化
+   - 移除默认的action上传方式
+   - 使用http-request自定义上传
+   - 添加完整的错误处理
+
+2. 上传流程优化
+   - 改为即时上传模式
+   - 上传成功后直接更新ID列表
+   - 失败时自动移除失败文件
+
+3. 请求处理优化
+   - 添加正确的Content-Type
+   - 完善响应处理逻辑
+   - 增加成功/失败提示
+
+4. 验证优化
+   - 保留文件类型和大小验证
+   - 提交时验证是否有上传图片
+   - 优化错误提示信息
+
+5. 界面布局优化
+   - 增加弹窗宽度至700px
+   - 缩小图片显示尺寸为120px
+   - 优化图片列表布局(flex布局)
+   - 添加图片间距
+
+6. 样式优化
+   - 统一图片尺寸
+   - 添加弹性布局
+   - 优化间距和对齐
+   - 美化提示文字
+
+1. 依赖更新
+   - 引入axios替代this.$http
+   - 添加上传进度处理
+
+2. 上传功能优化
+   - 添加上传进度显示
+   - 完善文件状态管理
+   - 优化错误处理逻辑
+
+3. 请求配置优化
+   - 添加Authorization头
+   - 配置上传进度回调
+   - 完善请求头设置
+
+4. 文件状态管理
+   - 添加文件上传状态
+   - 添加上传进度显示
+   - 优化文件列表更新逻辑
+
+5. 错误处理增强
+   - 完善错误信息展示
+   - 优化失败文件处理
+   - 添加文件状态更新 

+ 52 - 0
思路分析

@@ -0,0 +1,52 @@
+1. 功能需求拆解
+   - 在客户联系人下方添加联系人电话字段
+   - 联系人电话可以从下拉框选择时自动获取
+   - 允许手动输入客户联系人和电话
+   - 需要修改表单验证规则
+
+2. 技术实现要点
+   - 修改表单结构,添加新字段
+   - 添加联系人选择联动逻辑
+   - 调整验证规则
+   - 确保提交时包含新增字段
+
+3. UI交互设计
+   - 保持与现有表单风格一致
+   - 确保字段间的联动平滑 
+
+1. 修改上传组件配置
+   - 将limit改为3
+   - 调整照片预览样式
+
+2. 调整数据处理
+   - 修改checkImage字段存储格式
+   - 使用英文逗号拼接多个图片id
+   - 调整相关处理方法 
+
+1. 功能优化
+   - 添加多选功能
+   - 保持单张上传处理
+   - 控制上传数量限制
+
+2. 样式优化
+   - 增加弹窗宽度
+   - 优化图片显示布局
+   - 调整上传组件样式 
+
+1. 问题定位
+   - 当前上传请求未成功发送
+   - 需要自定义上传实现
+
+2. 解决方案
+   - 移除action属性
+   - 使用http-request自定义上传
+   - 完善错误处理 
+
+1. 问题修复
+   - 替换 $http 为 axios
+   - 优化异步上传逻辑
+
+2. 上传流程优化
+   - 使用 Promise.all 处理多文件
+   - 添加上传进度提示
+   - 完善错误处理 

Some files were not shown because too many files changed in this diff