tuqin
2026-09-24 409a6c46741422f0d6dc00acc38b5f7dc1f35e9f
feat: 现场需求前端改动

- 会议管理:顶部新增“会议看板提示”条(点击跳会议看板)
- 计量器具:校准模块新增“新增校准”(下拉选择计量器具并关联)
- 采购报表:支持按录入日期排序(PIMTable 支持服务端排序)
- 客户拜访记录:新增 新增/编辑/删除
- 设备报修:新增报修表单必填校验(设备名称)
- 部门管理:操作列新增 上移/下移 排序(两套页面)
- 报工台账:移除“参数详情”功能
已修改13个文件
672 ■■■■■ 文件已修改
src/api/collaborativeApproval/customerVisit.js 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/equipmentManagement/calibration.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/dept.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/PIMTable/PIMTable.vue 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/customerVisit/index.vue 176 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/meetingManagement/index.vue 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/calibration/index.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/measurementEquipment/components/calibrationDia.vue 122 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/repair/Modal/RepairModal.vue 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/SysAdmin/dept-manage/index.vue 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementReport/index.vue 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/index.vue 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dept/index.vue 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/collaborativeApproval/customerVisit.js
@@ -8,3 +8,29 @@
    params: query
  })
}
// 新增拜访记录
export function addVisit(data) {
  return request({
    url: '/customerVisits/add',
    method: 'post',
    data
  })
}
// 编辑拜访记录
export function updateVisit(data) {
  return request({
    url: '/customerVisits/update',
    method: 'post',
    data
  })
}
// 删除拜访记录
export function delVisit(id) {
  return request({
    url: '/customerVisits/' + id,
    method: 'delete'
  })
}
src/api/equipmentManagement/calibration.js
@@ -33,3 +33,10 @@
    data: ids,
  });
}
// 计量器具下拉(校准选择用,当前租户全部计量器具)
export function ledgerOptions() {
  return request({
    url: "/measuringInstrumentLedger/options",
    method: "get",
  });
}
src/api/system/dept.js
@@ -50,3 +50,12 @@
    method: 'delete'
  })
}
// 修改部门排序(批量)
export function updateDeptSort(data) {
  return request({
    url: '/system/dept/sort',
    method: 'put',
    data: data
  })
}
src/components/PIMTable/PIMTable.vue
@@ -19,6 +19,7 @@
            @current-change="currentChange"
            @selection-change="handleSelectionChange"
            @expand-change="expandChange"
            @sort-change="handleSortChange"
            class="lims-table">
    <el-table-column align="center"
                     type="selection"
@@ -41,7 +42,7 @@
                     :prop="item.prop"
                     :show-overflow-tooltip="item.dataType !== 'action' && item.dataType !== 'slot'"
                     :align="item.align"
                     :sortable="!!item.sortable"
                     :sortable="item.sortable || false"
                     :type="item.type"
                     :width="item.width"
                     :minWidth="item.minWidth">
@@ -215,6 +216,7 @@
    "expand-change",
    "selection-change",
    "row-click",
    "sort-change",
  ]);
  // Filters
@@ -500,6 +502,10 @@
  const handleSelectionChange = newSelection => {
    emit("selection-change", newSelection);
  };
  const handleSortChange = ({ column, prop, order }) => {
    emit("sort-change", { column, prop, order });
  };
</script>
<style scoped lang="scss">
src/views/collaborativeApproval/customerVisit/index.vue
@@ -24,6 +24,7 @@
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleQuery">搜索</el-button>
          <el-button type="primary" @click="handleAdd">新增</el-button>
        </el-form-item>
      </el-form>
    </div>
@@ -43,9 +44,11 @@
        <el-table-column label="拜访时间" prop="purposeDate" width="180" show-overflow-tooltip />
        <el-table-column label="拜访地点" prop="visitAddress" min-width="200" show-overflow-tooltip />
        <el-table-column label="拜访人" prop="visitingPeople" width="120" show-overflow-tooltip />
        <el-table-column fixed="right" label="操作" width="100" align="center">
        <el-table-column fixed="right" label="操作" width="160" align="center">
          <template #default="scope">
            <el-button link type="primary" size="small" @click="viewDetail(scope.row)" style="color: #67C23A">查看</el-button>
            <el-button link type="primary" size="small" @click="handleEdit(scope.row)">编辑</el-button>
            <el-button link type="danger" size="small" @click="handleDelete(scope.row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
@@ -124,13 +127,73 @@
        </div>
      </template>
    </el-dialog>
    <!-- 新增/编辑弹窗 -->
    <el-dialog
      v-model="formVisible"
      :title="formTitle"
      width="600px"
      @close="closeForm"
    >
      <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="客户名称:" prop="customerName">
              <el-input v-model="form.customerName" placeholder="请输入客户名称" clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="联系人:" prop="contact">
              <el-input v-model="form.contact" placeholder="请输入联系人" clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="联系电话:" prop="contactPhone">
              <el-input v-model="form.contactPhone" placeholder="请输入联系电话" clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="拜访人:" prop="visitingPeople">
              <el-input v-model="form.visitingPeople" placeholder="请输入拜访人" clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-form-item label="拜访目的:" prop="purposeVisit">
          <el-input v-model="form.purposeVisit" placeholder="请输入拜访目的" clearable />
        </el-form-item>
        <el-form-item label="拜访时间:" prop="purposeDate">
          <el-date-picker
            v-model="form.purposeDate"
            type="datetime"
            placeholder="请选择拜访时间"
            value-format="YYYY-MM-DD HH:mm:ss"
            format="YYYY-MM-DD HH:mm:ss"
            style="width: 100%"
          />
        </el-form-item>
        <el-form-item label="拜访地点:" prop="visitAddress">
          <el-input v-model="form.visitAddress" placeholder="请输入拜访地点" clearable />
        </el-form-item>
        <el-form-item label="备注:" prop="remark">
          <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注" />
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="closeForm">取消</el-button>
          <el-button type="primary" :loading="submitting" @click="submitForm">确定</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, getCurrentInstance } from 'vue'
import pagination from '@/components/PIMTable/Pagination.vue'
import { getVisitRecords } from '@/api/collaborativeApproval/customerVisit.js'
import { getVisitRecords, addVisit, updateVisit, delVisit } from '@/api/collaborativeApproval/customerVisit.js'
const { proxy } = getCurrentInstance()
const tableData = ref([])
@@ -194,6 +257,115 @@
  detailForm.value = {}
}
// ===== 新增 / 编辑 =====
const formVisible = ref(false)
const formTitle = ref('新增客户拜访记录')
const formRef = ref(null)
const submitting = ref(false)
const form = reactive({
  id: undefined,
  customerName: '',
  contact: '',
  contactPhone: '',
  visitingPeople: '',
  purposeVisit: '',
  purposeDate: '',
  visitAddress: '',
  remark: ''
})
const rules = {
  customerName: [{ required: true, message: '请输入客户名称', trigger: 'blur' }],
  purposeVisit: [{ required: true, message: '请输入拜访目的', trigger: 'blur' }],
  purposeDate: [{ required: true, message: '请选择拜访时间', trigger: 'change' }],
  visitAddress: [{ required: true, message: '请输入拜访地点', trigger: 'blur' }]
}
const resetForm = () => {
  Object.assign(form, {
    id: undefined,
    customerName: '',
    contact: '',
    contactPhone: '',
    visitingPeople: '',
    purposeVisit: '',
    purposeDate: '',
    visitAddress: '',
    remark: ''
  })
  formRef.value?.clearValidate()
}
// 新增
const handleAdd = () => {
  resetForm()
  formTitle.value = '新增客户拜访记录'
  formVisible.value = true
}
// 编辑
const handleEdit = (row) => {
  resetForm()
  Object.assign(form, {
    id: row.id,
    customerName: row.customerName,
    contact: row.contact,
    contactPhone: row.contactPhone,
    visitingPeople: row.visitingPeople,
    purposeVisit: row.purposeVisit,
    purposeDate: row.purposeDate,
    visitAddress: row.visitAddress,
    remark: row.remark
  })
  formTitle.value = '编辑客户拜访记录'
  formVisible.value = true
}
// 关闭弹窗
const closeForm = () => {
  formVisible.value = false
  resetForm()
}
// 提交(新增或编辑)
const submitForm = () => {
  formRef.value.validate((valid) => {
    if (!valid) return
    submitting.value = true
    const payload = { ...form }
    const req = payload.id ? updateVisit(payload) : addVisit(payload)
    req
      .then((res) => {
        if (res.code === 200) {
          proxy.$modal.msgSuccess(payload.id ? '编辑成功' : '新增成功')
          formVisible.value = false
          getList()
        } else {
          proxy.$modal.msgError(res.msg || '操作失败')
        }
      })
      .finally(() => {
        submitting.value = false
      })
  })
}
// 删除
const handleDelete = (row) => {
  proxy.$modal
    .confirm(`确认删除客户“${row.customerName}”的拜访记录吗?`)
    .then(() => {
      delVisit(row.id).then((res) => {
        if (res.code === 200) {
          proxy.$modal.msgSuccess('删除成功')
          getList()
        } else {
          proxy.$modal.msgError(res.msg || '删除失败')
        }
      })
    })
    .catch(() => {})
}
onMounted(() => {
  getList()
})
src/views/collaborativeApproval/meetingManagement/index.vue
@@ -1,5 +1,23 @@
<template>
  <div class="app-container">
    <!-- 会议看板提示条 -->
    <div
      class="meeting-board-tip"
      :class="{ 'is-active': stats.underWay > 0 || stats.toStart > 0 }"
      @click="goMeetingBoard"
    >
      <el-icon class="tip-icon"><BellFilled /></el-icon>
      <div class="tip-body">
        <span class="tip-title">会议看板提示</span>
        <span class="tip-text">
          共 <b>{{ stats.total }}</b> 场会议 ·
          进行中 <b class="num-ongoing">{{ stats.underWay }}</b> 场 ·
          即将开始 <b class="num-soon">{{ stats.toStart }}</b> 场
        </span>
      </div>
      <span class="tip-link">查看会议看板 ›</span>
    </div>
    <div class="tabs-wrapper">
      <el-tabs
        v-model="activeTab"
@@ -24,7 +42,10 @@
</template>
<script setup>
import { ref, computed } from 'vue'
import { ref, computed, onMounted, onActivated } from 'vue'
import { useRouter } from 'vue-router'
import { BellFilled } from '@element-plus/icons-vue'
import { getMeetSummary } from '@/api/collaborativeApproval/meeting.js'
import MeetSetting from '../notificationManagement/meetSetting/index.vue'
import MeetIndex from '../notificationManagement/meetIndex/index.vue'
import MeetApplication from '../notificationManagement/meetApplication/index.vue'
@@ -48,10 +69,110 @@
function handleTabChange(name) {
  activeTab.value = name
}
// 会议看板提示条数据
const router = useRouter()
const stats = ref({ total: 0, underWay: 0, completed: 0, toStart: 0 })
const loadMeetingStats = async () => {
  try {
    const { data } = await getMeetSummary()
    stats.value = {
      total: Number(data?.total) || 0,
      underWay: Number(data?.underWay) || 0,
      completed: Number(data?.completed) || 0,
      toStart: Number(data?.toStart) || 0
    }
  } catch (e) {
    // 提示条数据加载失败不影响会议管理主流程
    console.warn('会议看板提示加载失败:', e)
  }
}
const goMeetingBoard = () => {
  router.push('/collaborativeApproval/meetingBoard')
}
let tipInited = false
onMounted(loadMeetingStats)
onActivated(() => {
  // keep-alive 缓存时,切回页面刷新提示条
  if (tipInited) loadMeetingStats()
  tipInited = true
})
</script>
<style scoped lang="scss">
.meeting-board-tip {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 16px;
  margin-bottom: 12px;
  border-radius: 8px;
  cursor: pointer;
  background: #f4f8ff;
  border: 1px solid #d6e4ff;
  transition: background 0.2s, border-color 0.2s;
}
.meeting-board-tip:hover {
  background: #e8f1ff;
}
.meeting-board-tip.is-active {
  background: #fff7e6;
  border-color: #ffd591;
}
.meeting-board-tip .tip-icon {
  font-size: 18px;
  color: #409eff;
}
.meeting-board-tip.is-active .tip-icon {
  color: #fa8c16;
}
.meeting-board-tip .tip-body {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 12px;
}
.meeting-board-tip .tip-title {
  font-weight: 600;
  color: #1f2d3d;
  white-space: nowrap;
}
.meeting-board-tip .tip-text {
  font-size: 13px;
  color: #5b6b7f;
}
.meeting-board-tip .tip-text b {
  font-size: 15px;
  color: #409eff;
}
.meeting-board-tip .tip-text .num-ongoing {
  color: #fa8c16;
}
.meeting-board-tip .tip-text .num-soon {
  color: #13c2c2;
}
.meeting-board-tip .tip-link {
  font-size: 13px;
  color: #409eff;
  white-space: nowrap;
}
.tabs-wrapper {
  margin-bottom: 10px;
}
src/views/equipmentManagement/calibration/index.vue
@@ -37,6 +37,7 @@
                <el-button @click="handleReset" style="margin-left: 10px">重置</el-button>
            </div>
            <div>
                <el-button type="primary" @click="openCalibrationDia('add')">新增校准</el-button>
                <el-button @click="handleOut">导出</el-button>
            </div>
        </div>
src/views/equipmentManagement/measurementEquipment/components/calibrationDia.vue
@@ -2,7 +2,7 @@
    <div>
        <el-dialog
            v-model="dialogFormVisible"
            title="计量器具"
            :title="dialogTitle"
            width="50%"
            draggable
            @close="closeDia"
@@ -14,7 +14,28 @@
                :rules="rules"
                ref="formRef"
            >
                <el-row :gutter="30">
                <el-row :gutter="30" v-if="operationType === 'add'">
                    <el-col :span="24">
                        <el-form-item label="计量器具:" prop="measuringInstrumentLedgerId">
                            <el-select
                                v-model="form.measuringInstrumentLedgerId"
                                placeholder="请选择计量器具"
                                filterable
                                clearable
                                style="width: 100%"
                                @change="handleInstrumentChange"
                            >
                                <el-option
                                    v-for="item in instrumentOptions"
                                    :key="item.id"
                                    :label="`${item.code} - ${item.name}`"
                                    :value="item.id"
                                />
                            </el-select>
                        </el-form-item>
                    </el-col>
                </el-row>
                <el-row :gutter="30" v-else>
                    <el-col :span="12">
                        <el-form-item label="计量器具编号:" prop="customerName">
                            <el-input
@@ -99,23 +120,6 @@
                        </el-form-item>
                    </el-col>
                </el-row>
<!--                <el-row :gutter="30">-->
<!--                    <el-col :span="24">-->
<!--                        <el-form-item label="附件材料:" prop="remark">-->
<!--                            <el-upload v-model:file-list="fileList" :action="upload.url" multiple ref="fileUpload" auto-upload-->
<!--                                                 :headers="upload.headers" :before-upload="handleBeforeUpload" :on-error="handleUploadError"-->
<!--                                                 :on-success="handleUploadSuccess" :on-remove="handleRemove">-->
<!--                                <el-button type="primary" v-if="operationType !== 'view'">上传</el-button>-->
<!--                                <template #tip v-if="operationType !== 'view'">-->
<!--                                    <div class="el-upload__tip">-->
<!--                                        文件格式支持-->
<!--                                        doc,docx,xls,xlsx,ppt,pptx,pdf,txt,xml,jpg,jpeg,png,gif,bmp,rar,zip,7z-->
<!--                                    </div>-->
<!--                                </template>-->
<!--                            </el-upload>-->
<!--                        </el-form-item>-->
<!--                    </el-col>-->
<!--                </el-row>-->
            </el-form>
            <template #footer>
                <div class="dialog-footer">
@@ -128,13 +132,11 @@
</template>
<script setup>
import {ref, reactive, toRefs, getCurrentInstance} from "vue";
import {ref, reactive, toRefs, getCurrentInstance, computed} from "vue";
import useUserStore from "@/store/modules/user.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import {afterSalesServiceAdd, afterSalesServiceUpdate} from "@/api/customerService/index.js";
import {getToken} from "@/utils/auth.js";
import {ledgerRecordUpdate, ledgerRecordVerifying} from "@/api/equipmentManagement/calibration.js";
import {delLedgerFile} from "@/api/salesManagement/salesLedger.js";
import {ledgerRecordUpdate, ledgerRecordVerifying, ledgerOptions} from "@/api/equipmentManagement/calibration.js";
import { getCurrentDate } from "@/utils/index.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
@@ -142,10 +144,19 @@
const operationType = ref('')
const userStore = useUserStore();
const dialogTitle = computed(() => {
    if (operationType.value === 'add') return '新增校准'
    if (operationType.value === 'verifying') return '计量器具检定'
    return '编辑校准'
})
const data = reactive({
    form: {
        id: undefined,
        measuringInstrumentLedgerId: undefined,
        code: "",
        name: "",
        model: "",
        valid: "",
        recordDate: "",
        userId: "",
@@ -153,6 +164,7 @@
    tempFileIds: []
    },
    rules: {
        measuringInstrumentLedgerId: [{required: true, message: "请选择计量器具", trigger: "change"}],
        code: [{required: true, message: "请输入", trigger: "blur"}],
        name: [{required: true, message: "请输入", trigger: "blur"}],
        valid: [
@@ -188,6 +200,7 @@
})
const { form, rules } = toRefs(data);
const userList = ref([])
const instrumentOptions = ref([]);
const fileList = ref([]);
const upload = reactive({
    // 上传的地址
@@ -196,34 +209,69 @@
    headers: { Authorization: "Bearer " + getToken() },
});
// 加载计量器具下拉(当前租户全部计量器具)
const loadInstrumentOptions = () => {
    ledgerOptions().then((res) => {
        instrumentOptions.value = res.data || [];
    }).catch(() => {
        instrumentOptions.value = [];
    });
};
// 选择计量器具后回填编号/名称/证书编号,并记住台账ID(verifying 用 id 作为台账ID)
const handleInstrumentChange = (id) => {
    const it = instrumentOptions.value.find(o => o.id === id);
    if (it) {
        form.value.id = it.id;
        form.value.code = it.code;
        form.value.name = it.name;
        form.value.model = it.model;
    } else {
        form.value.id = undefined;
        form.value.code = "";
        form.value.name = "";
        form.value.model = "";
    }
};
// 打开弹框
const openDialog = (type, row) => {
  console.log(row)
const openDialog = (type, row = {}) => {
    operationType.value = type;
    dialogFormVisible.value = true;
    userListNoPageByTenantId().then((res) => {
        userList.value = res.data;
    });
  fileList.value = []
    if(type !== "add"){
    fileList.value = [];
    // 重置表单
    form.value.id = undefined;
    form.value.measuringInstrumentLedgerId = undefined;
    form.value.code = "";
    form.value.name = "";
    form.value.model = "";
    form.value.valid = "";
    form.value.recordDate = "";
      form.value.tempFileIds = [];
    if (type === "add") {
        loadInstrumentOptions();
  }
    if (type === "edit") {
        form.value.id = row.id;
        form.value.code = row.code;
        form.value.name = row.name;
        form.value.valid = row.valid;
        form.value.recordDate = row.recordDate;
    fileList.value = row.commonFiles;
    }
    if(type === "add"){
    fileList.value = row.commonFiles;
  }
    if(type === "verifying"){
        form.value.id = row.id;
        form.value.code = row.code;
        form.value.name = row.name;
        form.value.model = row.model;
        form.value.valid = row.valid;
        form.value.recordDate = row.mostDate;
    }
    form.value.id = row.id;
    form.value.code = row.code;
    form.value.name = row.name;
    form.value.userId = userStore.id;
    form.value.entryDate = getCurrentDate();
}
@@ -252,7 +300,13 @@
const submitForm = () => {
    proxy.$refs["formRef"].validate(valid => {
        if (valid) {
            if (operationType.value === "verifying") {
            if (operationType.value === "add") {
                // 新增校准:复用检定接口,写一条校准记录并同步更新台账检定日期/有效期
                ledgerRecordVerifying(form.value).then(response => {
                    proxy.$modal.msgSuccess("新增校准成功")
                    closeDia()
                })
            } else if (operationType.value === "verifying") {
                ledgerRecordVerifying(form.value).then(response => {
                    proxy.$modal.msgSuccess("检定校准成功")
                    closeDia()
src/views/equipmentManagement/repair/Modal/RepairModal.vue
@@ -6,11 +6,14 @@
              @confirm="sendForm"
              @cancel="handleCancel"
              @close="handleClose">
    <el-form :model="form"
    <el-form ref="formRef"
             :model="form"
             :rules="rules"
             label-width="100px">
      <el-row>
        <el-col :span="12">
          <el-form-item label="设备名称">
          <el-form-item label="设备名称"
                        prop="deviceLedgerId">
            <el-select v-model="form.deviceLedgerId"
                       @change="setDeviceModel"
                       filterable
@@ -163,6 +166,13 @@
  const userStore = useUserStore();
  const deviceOptions = ref([]);
  const fileList = ref([]);
  const formRef = ref();
  const rules = {
    deviceLedgerId: [
      { required: true, message: "请选择设备", trigger: "change" },
    ],
  };
  const loadDeviceName = async () => {
    const { data } = await getDeviceLedger();
@@ -204,6 +214,8 @@
  };
  const sendForm = async () => {
    const valid = await formRef.value.validate().catch(() => false);
    if (!valid) return;
    loading.value = true;
    try {
      const { code } = id.value
@@ -232,6 +244,7 @@
  const openAdd = async () => {
    id.value = undefined;
    operationType.value = "add";
    resetForm();
    visible.value = true;
    fileList.value = [];
    await nextTick();
src/views/officeProcessAutomation/SysAdmin/dept-manage/index.vue
@@ -69,6 +69,8 @@
            </el-table-column>
            <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
                <template #default="scope">
                    <el-button link type="primary" icon="Top" @click="moveUp(scope.row)" v-hasPermi="['system:dept:edit']">上移</el-button>
                    <el-button link type="primary" icon="Bottom" @click="moveDown(scope.row)" v-hasPermi="['system:dept:edit']">下移</el-button>
                    <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:dept:edit']">修改</el-button>
                    <el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['system:dept:add']">新增</el-button>
                    <el-button v-if="scope.row.parentId != 0" link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:dept:remove']">删除</el-button>
@@ -146,7 +148,7 @@
</template>
<script setup name="Dept">
import { listDept, getDept, delDept, addDept, updateDept, listDeptExcludeChild } from "@/api/system/dept"
import { listDept, getDept, delDept, addDept, updateDept, listDeptExcludeChild, updateDeptSort } from "@/api/system/dept"
const { proxy } = getCurrentInstance()
const { sys_normal_disable } = proxy.useDict("sys_normal_disable")
@@ -286,6 +288,57 @@
    }).catch(() => {})
}
/** 找到部门在同级中的位置 */
function findSiblings(targetId, list = deptList.value) {
    for (let i = 0; i < list.length; i++) {
        if (list[i].deptId === targetId) {
            return { list, index: i }
        }
        if (list[i].children && list[i].children.length) {
            const r = findSiblings(targetId, list[i].children)
            if (r) return r
        }
    }
    return null
}
/** 保存同级排序(重排 0..n-1) */
function saveDeptSort(siblings) {
    const payload = siblings.map((d, i) => ({ deptId: d.deptId, orderNum: i }))
    return updateDeptSort(payload).then(() => {
        proxy.$modal.msgSuccess("排序成功")
        getList()
    })
}
/** 上移 */
function moveUp(row) {
    const s = findSiblings(row.deptId)
    if (!s || s.index <= 0) {
        proxy.$modal.msgWarning("已经是第一个了")
        return
    }
    const { list, index } = s
    const tmp = list[index - 1]
    list[index - 1] = list[index]
    list[index] = tmp
    saveDeptSort(list)
}
/** 下移 */
function moveDown(row) {
    const s = findSiblings(row.deptId)
    if (!s || s.index >= s.list.length - 1) {
        proxy.$modal.msgWarning("已经是最后一个了")
        return
    }
    const { list, index } = s
    const tmp = list[index + 1]
    list[index + 1] = list[index]
    list[index] = tmp
    saveDeptSort(list)
}
getList()
</script>
src/views/procurementManagement/procurementReport/index.vue
@@ -70,6 +70,7 @@
          :is-show-pagination="true"
          :page="page"
          @pagination="handlePagination"
          @sort-change="handleSortChange"
        />
      </div>
    </el-card>
@@ -165,7 +166,8 @@
  {
    label: '录入日期',
    prop: 'entryDate',
    width: 120
    width: 120,
    sortable: 'custom'
  }
])
@@ -178,6 +180,22 @@
  current: 1,
  size: 50,
})
// 排序状态(按录入日期排序)
const sortState = reactive({ prop: '', order: '' })
// 表格排序变化
const handleSortChange = ({ prop, order }) => {
  if (order) {
    sortState.prop = prop
    sortState.order = order === 'ascending' ? 'asc' : 'desc'
  } else {
    sortState.prop = ''
    sortState.order = ''
  }
  page.current = 1
  handleSearch()
}
// 转换产品树数据,将 id 改为 value
function convertIdToValue(data) {
@@ -243,6 +261,12 @@
    params.current = page.current
    params.size = page.size
    // 排序参数(按录入日期)
    if (sortState.prop) {
      params.orderByColumn = sortState.prop
      params.isAsc = sortState.order
    }
    const res = await procurementBusinessSummaryListPage(params)
    if (res && res.data) {
      // 兼容后端可能直接返回数组/或返回分页对象
src/views/productionManagement/productionReporting/index.vue
@@ -123,31 +123,6 @@
    <input-modal v-if="isShowInput"
                 v-model:visible="isShowInput"
                 :production-product-main-id="isShowingId" />
    <!-- 参数详情弹窗 -->
    <el-dialog v-model="paramDetailVisible"
               title="参数详情"
               width="600px">
      <div v-if="currentParams && currentParams.length > 0"
           class="param-detail-list">
        <el-descriptions :column="1"
                         border>
          <el-descriptions-item v-for="param in currentParams"
                                :key="param.id"
                                :label="param.paramName">
            {{ param.inputValue }}
            <span v-if="param.unit && param.unit !== '/'"
                  class="unit-text">({{ param.unit }})</span>
          </el-descriptions-item>
        </el-descriptions>
      </div>
      <el-empty v-else
                description="暂无参数数据" />
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="paramDetailVisible = false">关闭</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
@@ -252,13 +227,6 @@
          },
        },
        {
          name: "参数详情",
          type: "text",
          clickFun: row => {
            showParamDetail(row);
          },
        },
        {
          name: "删除",
          type: "danger",
          clickFun: row => {
@@ -269,13 +237,6 @@
    },
  ]);
  const tableData = ref([]);
  const paramDetailVisible = ref(false);
  const currentParams = ref([]);
  const showParamDetail = row => {
    currentParams.value = row.productionOperationParamList || [];
    paramDetailVisible.value = true;
  };
  const selectedRows = ref([]);
  const tableLoading = ref(false);
  const childrenLoading = ref(false);
@@ -462,14 +423,6 @@
</script>
<style scoped>
  .unit-text {
    margin-left: 5px;
    color: #909399;
    font-size: 12px;
  }
  .param-detail-list {
    padding: 10px;
  }
  .table_list {
    margin-top: unset;
  }
src/views/system/dept/index.vue
@@ -68,6 +68,8 @@
            </el-table-column>
            <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
                <template #default="scope">
                    <el-button link type="primary" icon="Top" @click="moveUp(scope.row)" v-hasPermi="['system:dept:edit']">上移</el-button>
                    <el-button link type="primary" icon="Bottom" @click="moveDown(scope.row)" v-hasPermi="['system:dept:edit']">下移</el-button>
                    <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:dept:edit']">修改</el-button>
                    <el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['system:dept:add']">新增</el-button>
                    <el-button v-if="scope.row.parentId != 0" link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:dept:remove']">删除</el-button>
@@ -145,7 +147,7 @@
</template>
<script setup name="Dept">
import { listDept, getDept, delDept, addDept, updateDept, listDeptExcludeChild } from "@/api/system/dept"
import { listDept, getDept, delDept, addDept, updateDept, listDeptExcludeChild, updateDeptSort } from "@/api/system/dept"
const { proxy } = getCurrentInstance()
const { sys_normal_disable } = proxy.useDict("sys_normal_disable")
@@ -285,5 +287,56 @@
    }).catch(() => {})
}
/** 找到部门在同级中的位置 */
function findSiblings(targetId, list = deptList.value) {
    for (let i = 0; i < list.length; i++) {
        if (list[i].deptId === targetId) {
            return { list, index: i }
        }
        if (list[i].children && list[i].children.length) {
            const r = findSiblings(targetId, list[i].children)
            if (r) return r
        }
    }
    return null
}
/** 保存同级排序(重排 0..n-1) */
function saveDeptSort(siblings) {
    const payload = siblings.map((d, i) => ({ deptId: d.deptId, orderNum: i }))
    return updateDeptSort(payload).then(() => {
        proxy.$modal.msgSuccess("排序成功")
        getList()
    })
}
/** 上移 */
function moveUp(row) {
    const s = findSiblings(row.deptId)
    if (!s || s.index <= 0) {
        proxy.$modal.msgWarning("已经是第一个了")
        return
    }
    const { list, index } = s
    const tmp = list[index - 1]
    list[index - 1] = list[index]
    list[index] = tmp
    saveDeptSort(list)
}
/** 下移 */
function moveDown(row) {
    const s = findSiblings(row.deptId)
    if (!s || s.index >= s.list.length - 1) {
        proxy.$modal.msgWarning("已经是最后一个了")
        return
    }
    const { list, index } = s
    const tmp = list[index + 1]
    list[index + 1] = list[index]
    list[index] = tmp
    saveDeptSort(list)
}
getList()
</script>