0a9495113009836423b450f3598debd61eefa237..8f9ff13bd611dea65751c6eebaccd4e292c4c0c6
2 天以前 gongchunyi
feat: 已进行开票、回款操作的销售台账需限制不能编辑删除原有产品
8f9ff1 对比 | 目录
2 天以前 gaoluyang
进销存升级 1.选择台账时,若台账未开票和未来票金额为0时,开票登记和来票登记按钮置灰 2.文档管理分页展示有误 3.销售报价分页展示有误 4.新增销...
77444b 对比 | 目录
2 天以前 gaoluyang
进销存升级 1.选择台账时,若台账未开票和未来票金额为0时,开票登记和来票登记按钮置灰 2.文档管理分页展示有误 3.销售报价分页展示有误
db09e2 对比 | 目录
2 天以前 gongchunyi
fix: 优先显示msg
2b45d2 对比 | 目录
2 天以前 zhangwencui
Merge branch 'dev_New' of http://114.132.189.42:9002/r/product-inventory-ma...
816628 对比 | 目录
2 天以前 zhangwencui
打卡签到传参
5e6b7e 对比 | 目录
2 天以前 gaoluyang
Merge remote-tracking branch 'origin/dev_New' into dev_New
b99cca 对比 | 目录
2 天以前 gaoluyang
进销存升级 1.选择台账时,若台账未开票和未来票金额为0时,开票登记和来票登记按钮置灰 2.文档管理分页展示有误
02ffe8 对比 | 目录
2 天以前 zhangwencui
Merge branch 'dev_New' of http://114.132.189.42:9002/r/product-inventory-ma...
239307 对比 | 目录
2 天以前 zhangwencui
打卡规则配置
83946c 对比 | 目录
2 天以前 gaoluyang
Merge remote-tracking branch 'origin/dev_New' into dev_New
752b34 对比 | 目录
2 天以前 gaoluyang
进销存升级 1.选择台账时,若台账未开票和未来票金额为0时,开票登记和来票登记按钮置灰
4be8d8 对比 | 目录
2 天以前 gongchunyi
fix: 合同金额去重添加产品大类、规格型号
3aba73 对比 | 目录
2 天以前 gongchunyi
fix: 合同金额根据采购合同号、销售合同号进行去重
857b01 对比 | 目录
已添加3个文件
已修改8个文件
1007 ■■■■■ 文件已修改
index.html 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/attendanceRules.js 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/ImportExcel/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fileManagement/document/index.vue 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/attendanceCheckin/checkinRules/components/form.vue 461 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/attendanceCheckin/checkinRules/index.vue 293 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/attendanceCheckin/index.vue 45 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementInvoiceLedger/index.vue 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/invoiceRegistration/index.vue 62 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesLedger/index.vue 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesQuotation/index.vue 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
index.html
@@ -10,6 +10,10 @@
    />
    <link rel="icon" href="/favicon.ico" />
    <title>%VITE_APP_TITLE%</title>
    <!-- é«˜å¾·åœ°å›¾API -->
    <script type="text/javascript" src="https://webapi.amap.com/maps?v=2.0&key=6af5d2639adbbabf95eddfbf2bae5739"></script>
    <!-- é«˜å¾·åœ°å›¾æœç´¢æ’ä»¶ -->
    <script type="text/javascript" src="https://webapi.amap.com/loca?v=2.0.0&key=6af5d2639adbbabf95eddfbf2bae5739"></script>
    <!--[if lt IE 11
      ]><script>
        window.location.href = "/html/ie.html";
src/api/personnelManagement/attendanceRules.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,45 @@
import request from "@/utils/request";
// èŽ·å–æ‰“å¡è§„åˆ™åˆ—è¡¨
export function getAttendanceRules(query) {
  return request({
    url: "/personalAttendanceLocationConfig/listPage",
    method: "get",
    params: query,
  });
}
// æ–°å¢žæ‰“卡规则
export function addAttendanceRule(data) {
  return request({
    url: "/personalAttendanceLocationConfig/add",
    method: "post",
    data,
  });
}
// æ›´æ–°æ‰“卡规则
export function updateAttendanceRule(data) {
  return request({
    url: "/attendanceRules/update",
    method: "put",
    data,
  });
}
// åˆ é™¤æ‰“卡规则
export function deleteAttendanceRule(ids) {
  return request({
    url: `/personalAttendanceLocationConfig/del`,
    method: "delete",
    data: ids,
  });
}
// èŽ·å–å•ä¸ªæ‰“å¡è§„åˆ™è¯¦æƒ…
export function getAttendanceRuleDetail(id) {
  return request({
    url: `/attendanceRules/detail/${id}`,
    method: "get",
  });
}
src/views/basicData/product/ImportExcel/index.vue
@@ -70,7 +70,7 @@
const handleFileSuccess = (response) => {
  const { code, msg } = response;
  if (code == 200) {
    ElMessage({ message: "导入成功", type: "success" });
    ElMessage({ message: msg || "导入成功", type: "success" });
    upload.open = false;
    emits("uploadSuccess");
  } else {
src/views/fileManagement/document/index.vue
@@ -107,7 +107,6 @@
            current: pagination.currentPage,
            size: pagination.pageSize,
            total: pagination.total,
            layout: 'total, sizes, prev, pager, next, jumper'
          }"
          @selection-change="handleSelectionChange"
          @pagination="handlePagination"
@@ -1137,9 +1136,9 @@
    
    // æž„建查询参数
    const query = {
      page: pagination.currentPage,
      current: pagination.currentPage,
      size: pagination.pageSize,
      documentClassificationId:currentId.value
      documentClassificationId: currentId.value
    };
    
    const res = await getDocumentList(query);
@@ -1166,9 +1165,10 @@
};
// å¤„理分页变化
const handlePagination = (current, size) => {
  pagination.currentPage = current;
  pagination.pageSize = size;
const handlePagination = (payload) => {
  // PIMTable emit: { page, limit }
  pagination.currentPage = payload?.page || 1;
  pagination.pageSize = payload?.limit || pagination.pageSize;
  loadDocumentList();
};
src/views/personnelManagement/attendanceCheckin/checkinRules/components/form.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,461 @@
<template>
  <el-dialog v-model="dialogVisible"
             :title="dialogTitle"
             width="700px"
             :close-on-click-modal="false">
    <el-form ref="formRef"
             :model="form"
             :rules="rules"
             label-width="120px"
             class="mt8">
      <!-- éƒ¨é—¨é€‰æ‹© -->
      <el-form-item label="部门"
                    prop="sysDeptId">
        <el-tree-select v-model="form.sysDeptId"
                        :data="deptOptions"
                        :props="{ value: 'id', label: 'label', children: 'children' }"
                        value-key="id"
                        placeholder="请选择部门"
                        check-strictly
                        style="width: 100%"
                        :disabled="operationType === 'view'" />
      </el-form-item>
      <!-- åœ°ç‚¹ä¿¡æ¯ -->
      <!-- <el-form-item label="地点名称"
                    prop="locationName">
        <el-input v-model="form.locationName"
                  placeholder="请输入地点名称"
                  :disabled="operationType === 'view'" />
      </el-form-item> -->
      <!-- æ‰“卡范围 -->
      <el-form-item label="打卡范围(m)"
                    prop="radius">
        <el-input-number v-model="form.radius"
                         :min="10"
                         :max="1000"
                         :step="10"
                         placeholder="请输入打卡范围"
                         :disabled="operationType === 'view'" />
      </el-form-item>
      <!-- é«˜å¾·åœ°å›¾é€‰æ‹© -->
      <el-form-item label="打卡位置"
                    prop="longitude">
        <div class="map-container">
          <div class="map-header"
               style="margin-bottom: 10px">
            <el-button @click="getCurrentLocation">
              <el-icon>
                <Position />
              </el-icon>
              å½“前位置
            </el-button>
            <span style="margin-left: 10px; color: #909399;font-size: 12px;">点击地图选择位置</span>
          </div>
          <div id="map-container"
               class="map"
               ref="mapContainer"></div>
          <div class="coordinates-info mt10">
            <el-input v-model="form.longitude"
                      readonly
                      placeholder="经度"
                      style="width: 140px; margin-right: 10px" />
            <el-input v-model="form.latitude"
                      readonly
                      placeholder="纬度"
                      style="width: 140px; margin-right: 10px" />
            <!-- <el-input v-model="form.locationName"
                      placeholder="地点名称"
                      style="width: calc(100% - 290px)" /> -->
          </div>
        </div>
      </el-form-item>
      <el-form-item label="地点名称"
                    prop="locationName">
        <el-input v-model="form.locationName"
                  :disabled="operationType === 'view'"
                  placeholder="请输入地点名称" />
      </el-form-item>
      <!-- ä¸Šä¸‹ç­æ—¶é—´ -->
      <el-form-item label="上班时间"
                    prop="startAt">
        <el-time-picker v-model="form.startAt"
                        format="HH:mm"
                        value-format="HH:mm"
                        placeholder="请选择上班时间"
                        :disabled="operationType === 'view'" />
      </el-form-item>
      <el-form-item label="下班时间"
                    prop="endAt">
        <el-time-picker v-model="form.endAt"
                        format="HH:mm"
                        value-format="HH:mm"
                        placeholder="请选择下班时间"
                        :disabled="operationType === 'view'" />
      </el-form-item>
    </el-form>
    <template #footer>
      <span class="dialog-footer">
        <el-button @click="dialogVisible = false">取消</el-button>
        <el-button type="primary"
                   @click="submitForm"
                   v-if="operationType !== 'view'">
          ç¡®å®š
        </el-button>
      </span>
    </template>
  </el-dialog>
</template>
<script setup>
  import { ref, reactive, computed, watch, onMounted, nextTick } from "vue";
  import { ElMessage } from "element-plus";
  import { Position } from "@element-plus/icons-vue";
  import { deptTreeSelect } from "@/api/system/user.js";
  import { addAttendanceRule } from "@/api/personnelManagement/attendanceRules.js";
  const props = defineProps({
    modelValue: {
      type: Boolean,
      default: false,
    },
    operationType: {
      type: String,
      default: "add",
    },
    row: {
      type: Object,
      default: () => ({}),
    },
  });
  const emit = defineEmits(["update:modelValue", "close"]);
  const dialogVisible = computed({
    get: () => props.modelValue,
    set: val => emit("update:modelValue", val),
  });
  const dialogTitle = computed(() => {
    if (props.operationType === "add") return "新增打卡规则";
    if (props.operationType === "edit") return "编辑打卡规则";
    return "查看打卡规则";
  });
  // è¡¨å•数据
  const formRef = ref();
  const form = reactive({
    id: "",
    sysDeptId: "",
    locationName: "",
    longitude: "",
    latitude: "",
    radius: 100,
    startAt: "09:00",
    endAt: "18:00",
  });
  // è¡¨å•验证规则
  const rules = {
    sysDeptId: [{ required: true, message: "请选择部门", trigger: "change" }],
    locationName: [
      { required: true, message: "请输入地点名称", trigger: "blur" },
    ],
    longitude: [{ required: true, message: "请选择打卡位置", trigger: "blur" }],
    latitude: [{ required: true, message: "请选择打卡位置", trigger: "blur" }],
    radius: [{ required: true, message: "请输入打卡范围", trigger: "blur" }],
    startAt: [{ required: true, message: "请选择上班时间", trigger: "change" }],
    endAt: [{ required: true, message: "请选择下班时间", trigger: "change" }],
  };
  // éƒ¨é—¨é€‰é¡¹
  const deptOptions = ref([]);
  // åœ°å›¾ç›¸å…³
  const mapContainer = ref(null);
  let map = null;
  let marker = null;
  let circle = null;
  // èŽ·å–éƒ¨é—¨åˆ—è¡¨
  const fetchDeptOptions = () => {
    deptTreeSelect().then(response => {
      deptOptions.value = filterDisabledDept(
        JSON.parse(JSON.stringify(response.data))
      );
    });
  };
  // è¿‡æ»¤ç¦ç”¨çš„部门
  const filterDisabledDept = deptList => {
    return deptList.filter(dept => {
      if (dept.disabled) {
        return false;
      }
      if (dept.children && dept.children.length) {
        dept.children = filterDisabledDept(dept.children);
      }
      return true;
    });
  };
  // åˆå§‹åŒ–地图
  const initMap = () => {
    nextTick(() => {
      if (window.AMap && mapContainer.value) {
        // åˆå§‹åŒ–地图
        map = new window.AMap.Map(mapContainer.value, {
          zoom: 16,
          center: [116.397428, 39.90923], // é»˜è®¤åŒ—京
        });
        // æ·»åŠ æŽ§ä»¶
        window.AMap.plugin(["AMap.ToolBar", "AMap.Scale"], function () {
          map.addControl(new window.AMap.ToolBar());
          map.addControl(new window.AMap.Scale());
        });
        // æ·»åŠ æ ‡è®°
        marker = new window.AMap.Marker({
          position: [116.397428, 39.90923],
          draggable: true,
          cursor: "move",
          title: "拖拽定位",
        });
        map.add(marker);
        // æ·»åŠ åœ†å½¢èŒƒå›´
        circle = new window.AMap.Circle({
          center: [116.397428, 39.90923],
          radius: form.radius,
          strokeColor: "#3366FF",
          strokeOpacity: 0.8,
          strokeWeight: 2,
          fillColor: "#3366FF",
          fillOpacity: 0.2,
        });
        map.add(circle);
        // ç›‘听标记拖拽
        marker.on("dragend", e => {
          const position = e.lnglat;
          const lng = position.getLng();
          const lat = position.getLat();
          form.longitude = lng;
          form.latitude = lat;
          updateCircle(position);
        });
        // ç›‘听标记拖拽开始
        marker.on("dragstart", () => {
          map.setDefaultCursor("move");
        });
        // ç›‘听标记拖拽结束
        marker.on("dragend", () => {
          map.setDefaultCursor("default");
        });
        // ç›‘听地图点击
        map.on("click", e => {
          const position = e.lnglat;
          const lng = position.getLng();
          const lat = position.getLat();
          form.longitude = lng;
          form.latitude = lat;
          updateMarker(position);
          updateCircle(position);
        });
        // å°è¯•获取当前位置并设置为地图中心
        if (navigator.geolocation && !form.longitude && !form.latitude) {
          navigator.geolocation.getCurrentPosition(
            position => {
              console.log("获取到当前位置:", position);
              const { longitude, latitude } = position.coords;
              const currentPosition = [longitude, latitude];
              map.setCenter(currentPosition);
              updateMarker(currentPosition);
              updateCircle(currentPosition);
              form.longitude = longitude;
              form.latitude = latitude;
            },
            error => {
              console.log("获取位置失败,使用默认位置");
            }
          );
        } else if (form.longitude && form.latitude) {
          // å¦‚果有数据,设置到地图
          const position = [form.longitude, form.latitude];
          map.setCenter(position);
          updateMarker(position);
          updateCircle(position);
        }
      }
    });
  };
  // æ›´æ–°æ ‡è®°ä½ç½®
  const updateMarker = position => {
    if (marker) {
      marker.setPosition(position);
    }
  };
  // æ›´æ–°åœ†å½¢èŒƒå›´
  const updateCircle = position => {
    if (circle) {
      circle.setCenter(position);
      circle.setRadius(form.radius);
    }
  };
  // èŽ·å–å½“å‰ä½ç½®
  const getCurrentLocation = () => {
    if (navigator.geolocation) {
      navigator.geolocation.getCurrentPosition(
        position => {
          const { longitude, latitude } = position.coords;
          form.longitude = longitude;
          form.latitude = latitude;
          if (map) {
            map.setCenter([longitude, latitude]);
            updateMarker([longitude, latitude]);
            updateCircle([longitude, latitude]);
          }
          // é€†åœ°ç†ç¼–码获取地址
          if (window.AMap) {
            // åŠ è½½Geocoder插件
            window.AMap.plugin("AMap.Geocoder", function () {
              const geocoder = new window.AMap.Geocoder();
              geocoder.getAddress([longitude, latitude], (status, result) => {
                if (status === "complete" && result.regeocode) {
                  form.locationName = result.regeocode.formattedAddress;
                }
              });
            });
          }
        },
        error => {
          ElMessage.error("获取位置失败,请手动选择");
        }
      );
    } else {
      ElMessage.error("浏览器不支持地理定位");
    }
  };
  // ç›‘听半径变化
  watch(
    () => form.radius,
    newValue => {
      if (circle) {
        circle.setRadius(newValue);
      }
    }
  );
  // ç›‘听弹窗显示
  watch(
    () => dialogVisible.value,
    newValue => {
      if (newValue) {
        // é‡ç½®è¡¨å•
        Object.assign(form, {
          id: "",
          sysDeptId: "",
          locationName: "",
          longitude: "",
          latitude: "",
          radius: 100,
          startAt: "09:00",
          endAt: "18:00",
        });
        // å¦‚果是编辑或查看,填充数据
        if (props.operationType !== "add" && props.row.id) {
          // å¤„理时间格式,确保是HH:mm格式
          const rowData = { ...props.row };
          if (rowData.startAt && rowData.startAt.includes(":")) {
            rowData.startAt = rowData.startAt.split(":").slice(0, 2).join(":");
          }
          if (rowData.endAt && rowData.endAt.includes(":")) {
            rowData.endAt = rowData.endAt.split(":").slice(0, 2).join(":");
          }
          Object.assign(form, rowData);
        }
        // åˆå§‹åŒ–地图
        setTimeout(() => {
          initMap();
        }, 100);
      }
    }
  );
  // æäº¤è¡¨å•
  const submitForm = () => {
    formRef.value.validate(valid => {
      if (valid) {
        const submitData = {
          ...form,
          // è½¬æ¢æ—¶é—´æ ¼å¼ï¼Œç¡®ä¿åªä¿ç•™æ—¶åˆ†éƒ¨åˆ†
          startAt: form.startAt
            ? `${form.startAt.split(":").slice(0, 2).join(":")}`
            : null,
          endAt: form.endAt
            ? `${form.endAt.split(":").slice(0, 2).join(":")}`
            : null,
        };
        if (props.operationType === "add") {
          addAttendanceRule(submitData).then(() => {
            ElMessage.success("新增成功");
            emit("close");
          });
        } else if (props.operationType === "edit") {
          addAttendanceRule(submitData).then(() => {
            ElMessage.success("更新成功");
            emit("close");
          });
        }
      }
    });
  };
  // åˆå§‹åŒ–
  onMounted(() => {
    fetchDeptOptions();
  });
</script>
<style scoped lang="scss">
  .map-container {
    width: 100%;
  }
  .map {
    width: 100%;
    height: 400px;
    border: 1px solid #e4e7ed;
  }
  .coordinates-info {
    display: flex;
    gap: 10px;
  }
  .coordinates-display {
    padding: 10px;
    background-color: #f5f7fa;
    border-radius: 4px;
  }
  .mt10 {
    margin-top: 10px;
  }
  .mt8 {
    margin-top: 8px;
  }
</style>
src/views/personnelManagement/attendanceCheckin/checkinRules/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,293 @@
<template>
  <div class="app-container">
    <!-- é¡µé¢æ ‡é¢˜å’Œæ“ä½œæŒ‰é’® -->
    <div class="page-header">
      <div class="title">打卡规则配置</div>
      <div class="actions">
        <el-button type="primary"
                   @click="openForm('add')">
          <el-icon>
            <Plus />
          </el-icon>
          æ–°å¢žè§„则
        </el-button>
      </div>
    </div>
    <!-- æŸ¥è¯¢æ¡ä»¶ -->
    <!-- <el-form :model="searchForm"
             :inline="true"
             class="search-form mb16">
      <el-form-item label="部门:"
                    prop="countId">
        <el-tree-select v-model="searchForm.countId"
                        :data="deptOptions"
                        :props="{ value: 'id', label: 'label', children: 'children' }"
                        value-key="id"
                        placeholder="请选择部门"
                        check-strictly
                        style="width: 200px" />
      </el-form-item>
      <el-form-item label="地点:"
                    prop="locationName">
        <el-input v-model="searchForm.locationName"
                  placeholder="请输入地点名称"
                  clearable
                  style="width: 200px" />
      </el-form-item>
      <el-form-item>
        <el-button type="primary"
                   @click="fetchData">
          <el-icon>
            <Search />
          </el-icon>
          æœç´¢
        </el-button>
        <el-button @click="resetSearch">
          <el-icon>
            <Refresh />
          </el-icon>
          é‡ç½®
        </el-button>
      </el-form-item>
    </el-form> -->
    <!-- è§„则列表 -->
    <el-card shadow="never"
             class="mb16">
      <el-table :data="tableData"
                border
                v-loading="tableLoading"
                style="width: 100%"
                row-key="id">
        <el-table-column type="index"
                         label="序号"
                         width="60"
                         align="center" />
        <el-table-column label="部门">
          <template #default="scope">
            {{ getDeptNameById(scope.row.sysDeptId) }}
          </template>
        </el-table-column>
        <el-table-column prop="locationName"
                         label="地点名称" />
        <el-table-column prop="longitude"
                         label="经度" />
        <el-table-column prop="latitude"
                         label="纬度" />
        <el-table-column prop="radius"
                         label="打卡范围(m)" />
        <el-table-column prop="startAt"
                         label="上班时间">
          <template #default="scope">
            {{ scope.row.startAt }}
          </template>
        </el-table-column>
        <el-table-column prop="endAt"
                         label="下班时间">
          <template #default="scope">
            {{ scope.row.endAt }}
          </template>
        </el-table-column>
        <el-table-column label="操作"
                         width="180"
                         fixed="right"
                         align="center">
          <template #default="scope">
            <el-button type="primary"
                       size="small"
                       link
                       @click="openForm('edit', scope.row)">编辑</el-button>
            <el-button type="danger"
                       size="small"
                       link
                       @click="handleDelete(scope.row.id)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <pagination :total="page.total"
                  layout="total, sizes, prev, pager, next, jumper"
                  :page="page.current"
                  :limit="page.size"
                  @pagination="paginationChange"
                  class="mt10" />
    </el-card>
    <!-- æ–°å¢ž/编辑规则弹窗 -->
    <rule-form ref="ruleFormRef"
               v-model="dialogVisible"
               :operation-type="operationType"
               :row="currentRow"
               @close="dialogVisible = false; fetchData()" />
  </div>
</template>
<script setup>
  import { ref, reactive, onMounted } from "vue";
  import { ElMessage, ElMessageBox } from "element-plus";
  import { Plus, Edit, Delete, Search, Refresh } from "@element-plus/icons-vue";
  import Pagination from "@/components/Pagination/index.vue";
  import RuleForm from "./components/form.vue";
  import { deptTreeSelect } from "@/api/system/user.js";
  import {
    getAttendanceRules,
    deleteAttendanceRule,
  } from "@/api/personnelManagement/attendanceRules.js";
  const { proxy } = getCurrentInstance();
  // è¡¨æ ¼æ•°æ®
  const tableData = ref([]);
  const tableLoading = ref(false);
  // åˆ†é¡µå‚æ•°
  const page = reactive({
    current: 1,
    size: 10,
    total: 0,
  });
  // æŸ¥è¯¢è¡¨å•
  const searchForm = reactive({
    countId: "",
    locationName: "",
  });
  // éƒ¨é—¨é€‰é¡¹
  const deptOptions = ref([]);
  // å¼¹çª—控制
  const dialogVisible = ref(false);
  const operationType = ref("add");
  const currentRow = ref({});
  const ruleFormRef = ref();
  // æ ¼å¼åŒ–æ—¶é—´
  const formatTime = timestamp => {
    if (!timestamp) return "";
    const date = new Date(timestamp);
    return `${String(date.getHours()).padStart(2, "0")}:${String(
      date.getMinutes()
    ).padStart(2, "0")}`;
  };
  // èŽ·å–éƒ¨é—¨åˆ—è¡¨
  const fetchDeptOptions = () => {
    deptTreeSelect().then(response => {
      deptOptions.value = filterDisabledDept(
        JSON.parse(JSON.stringify(response.data))
      );
    });
  };
  // è¿‡æ»¤ç¦ç”¨çš„部门
  const filterDisabledDept = deptList => {
    return deptList.filter(dept => {
      if (dept.disabled) {
        return false;
      }
      if (dept.children && dept.children.length) {
        dept.children = filterDisabledDept(dept.children);
      }
      return true;
    });
  };
  // æ ¹æ®éƒ¨é—¨ID查找部门名称
  const getDeptNameById = (deptId, deptList = deptOptions.value) => {
    for (const dept of deptList) {
      if (dept.id === deptId) {
        return dept.label;
      }
      if (dept.children && dept.children.length) {
        const name = getDeptNameById(deptId, dept.children);
        if (name) {
          return name;
        }
      }
    }
    return "";
  };
  // æŸ¥è¯¢è§„则列表
  const fetchData = () => {
    tableLoading.value = true;
    getAttendanceRules({ ...page, ...searchForm })
      .then(res => {
        tableData.value = res.data.records;
        page.total = res.data.total;
      })
      .finally(() => {
        tableLoading.value = false;
      });
  };
  // åˆ†é¡µå˜æ›´
  const paginationChange = pagination => {
    page.current = pagination.page;
    page.size = pagination.limit;
    fetchData();
  };
  // é‡ç½®æœç´¢
  const resetSearch = () => {
    searchForm.countId = "";
    searchForm.locationName = "";
    fetchData();
  };
  // æ‰“开表单
  const openForm = (type, row = {}) => {
    operationType.value = type;
    currentRow.value = row;
    dialogVisible.value = true;
  };
  // åˆ é™¤è§„则
  const handleDelete = id => {
    ElMessageBox.confirm("确定要删除这条规则吗?", "删除确认", {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        deleteAttendanceRule([id]).then(() => {
          ElMessage.success("删除成功");
          fetchData();
        });
      })
      .catch(() => {
        // å–消删除
      });
  };
  // åˆå§‹åŒ–
  onMounted(() => {
    fetchDeptOptions();
    fetchData();
  });
</script>
<style scoped lang="scss">
  .page-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
    .title {
      font-size: 18px;
      font-weight: 600;
    }
    .actions {
      display: flex;
      gap: 10px;
    }
  }
  .mb16 {
    margin-bottom: 16px;
  }
  .mt10 {
    margin-top: 10px;
  }
</style>
src/views/personnelManagement/attendanceCheckin/index.vue
@@ -5,7 +5,8 @@
             class="mb16">
      <div class="attendance-header">
        <div>
          <div class="title">打卡签到</div>
          <div class="title">打卡签到
          </div>
          <div class="sub-title">支持一键打卡,自动记录上下班时间</div>
        </div>
        <div class="attendance-actions">
@@ -341,13 +342,45 @@
      });
  };
  // èŽ·å–å½“å‰ä½ç½®
  const getCurrentLocation = () => {
    return new Promise((resolve, reject) => {
      if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(
          position => {
            const { longitude, latitude } = position.coords;
            resolve({ longitude, latitude });
          },
          error => {
            console.log("获取位置失败:", error);
            reject(error);
          }
        );
      } else {
        reject(new Error("浏览器不支持地理定位"));
      }
    });
  };
  // æ‰“卡
  const handleCheckInOut = () => {
    createPersonalAttendanceRecord({}).then(res => {
      fetchData();
      fetchTodayData();
      ElMessage.success("打卡成功!");
    });
    getCurrentLocation()
      .then(location => {
        createPersonalAttendanceRecord(location).then(res => {
          fetchData();
          fetchTodayData();
          ElMessage.success("打卡成功!");
        });
      })
      .catch(error => {
        // èŽ·å–ä½ç½®å¤±è´¥æ—¶ï¼Œä»å…è®¸æ‰“å¡
        ElMessage.warning("获取位置失败,将使用默认位置打卡");
        createPersonalAttendanceRecord({}).then(res => {
          fetchData();
          fetchTodayData();
          ElMessage.success("打卡成功!");
        });
      });
  };
  onMounted(() => {
src/views/procurementManagement/procurementInvoiceLedger/index.vue
@@ -237,21 +237,32 @@
  }
);
// ä¸»è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable = (param) => {
  return proxy.summarizeTable(
  const sums = proxy.summarizeTable(
    param,
    [
      "taxInclusiveTotalPrice",
      "ticketsAmount",
      "unTicketsPrice",
      "invoiceAmount",
    ],
    ["ticketsAmount", "unTicketsPrice", "invoiceAmount"],
    {
      ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
      futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
      ticketsNum: { noDecimal: true },
      futureTickets: { noDecimal: true },
    }
  );
  const keySet = new Set();
  let taxInclusiveSum = 0;
  (param.data || []).forEach((row) => {
    const key = `${row.purchaseContractNumber ?? ""}\n${row.salesContractNo ?? ""}\n${row.productCategory ?? ""}\n${row.specificationModel ?? ""}`;
    if (keySet.has(key)) return;
    keySet.add(key);
    const val = Number(row.taxInclusiveTotalPrice);
    if (!isNaN(val)) taxInclusiveSum += val;
  });
  const taxInclusiveIndex = (param.columns || []).findIndex(
    (c) => c.property === "taxInclusiveTotalPrice"
  );
  if (taxInclusiveIndex !== -1) {
    sums[taxInclusiveIndex] = taxInclusiveSum.toFixed(2);
  }
  return sums;
};
const handleSelectionChange = (val) => {
src/views/salesManagement/invoiceRegistration/index.vue
@@ -30,7 +30,12 @@
            <div class="flex justify-between">
                <div></div>
                <div>
                    <el-button type="primary" @click="openForm" style="margin-bottom: 8px">
                    <el-button
                        type="primary"
                        @click="openForm"
                        style="margin-bottom: 8px"
                        :disabled="!canInvoice"
                    >
                        å¼€ç¥¨ç™»è®°
                    </el-button>
                </div>
@@ -296,10 +301,14 @@
                    />
                    <el-table-column label="本次开票数" prop="currentInvoiceNum" width="180">
                        <template #default="scope">
                            <el-input-number :step="0.1" :min="0" style="width: 100%"
                                                             :precision="2"
                                                             v-model="scope.row.currentInvoiceNum"
                                                             @change="invoiceNumBlur(scope.row)"
                            <el-input-number
                                :step="0.1"
                                :min="0"
                                style="width: 100%"
                                :precision="2"
                                v-model="scope.row.currentInvoiceNum"
                                @change="invoiceNumBlur(scope.row)"
                                :disabled="isProductInvoiceDisabled(scope.row)"
                            ></el-input-number>
                        </template>
                    </el-table-column>
@@ -309,10 +318,14 @@
                        width="180"
                    >
                        <template #default="scope">
                            <el-input-number :step="0.01" :min="0" style="width: 100%"
                                                             :precision="2"
                                                             v-model="scope.row.currentInvoiceAmount"
                                                             @change="invoiceAmountBlur(scope.row)"
                            <el-input-number
                                :step="0.01"
                                :min="0"
                                style="width: 100%"
                                :precision="2"
                                v-model="scope.row.currentInvoiceAmount"
                                @change="invoiceAmountBlur(scope.row)"
                                :disabled="isProductInvoiceDisabled(scope.row)"
                            ></el-input-number>
                        </template>
                    </el-table-column>
@@ -375,7 +388,7 @@
<script setup>
import pagination from "@/components/PIMTable/Pagination.vue";
import FormDialog from '@/components/Dialog/FormDialog.vue';
import { onMounted, ref } from "vue";
import { onMounted, ref, computed } from "vue";
import { Search } from "@element-plus/icons-vue";
import { ElMessageBox } from "element-plus";
// import {userListNoPage} from "@/api/system/user.js";
@@ -450,6 +463,27 @@
const formattedInputNumber = (value) => {
    return value ? parseFloat(value).toFixed(2) : 0;
};
// åˆ¤æ–­æ˜¯å¦å¯ä»¥å¼€ç¥¨ï¼ˆåŸºäºŽé€‰ä¸­çš„台账数据)
const canInvoice = computed(() => {
    if (selectedRows.value.length === 0) {
        return false;
    }
    // æ£€æŸ¥æ‰€æœ‰é€‰ä¸­çš„台账,只要有一个未开票金额大于0,就可以开票
    return selectedRows.value.some(row => {
        const noInvoiceAmount = parseFloat(row.noInvoiceAmountTotal || 0);
        return noInvoiceAmount > 0;
    });
});
// åˆ¤æ–­å•个产品是否可以开票
const isProductInvoiceDisabled = (row) => {
    // æ£€æŸ¥æœªå¼€ç¥¨é‡‘额和未开票数,如果都为0或小于等于0,则禁用
    // ä¼˜å…ˆä½¿ç”¨ tempnoInvoiceAmount å’Œ tempNoInvoiceNum(初始值),如果没有则使用 noInvoiceAmount å’Œ originalNoInvoiceNum
    const noInvoiceAmount = parseFloat(row.tempnoInvoiceAmount || row.noInvoiceAmount || 0);
    const noInvoiceNum = parseFloat(row.tempNoInvoiceNum || row.originalNoInvoiceNum || row.noInvoiceNum || 0);
    return noInvoiceAmount <= 0 || noInvoiceNum <= 0;
};
// æŸ¥è¯¢åˆ—表
@@ -579,6 +613,14 @@
        
        productData.value = allProductData;
        
        // å¯¹äºŽä¸èƒ½å¼€ç¥¨çš„产品,将开票数和开票金额设置为0
        productData.value.forEach(item => {
            if (isProductInvoiceDisabled(item)) {
                item.currentInvoiceNum = 0;
                item.currentInvoiceAmount = 0;
            }
        });
        dialogFormVisible.value = true;
        console.log("productData.value ", productData.value);
    });
src/views/salesManagement/salesLedger/index.vue
@@ -121,7 +121,7 @@
        <el-table-column label="备注" prop="remarks" width="200" show-overflow-tooltip />
        <el-table-column fixed="right" label="操作" min-width="100" align="center">
          <template #default="scope">
            <el-button link type="primary" size="small" @click="openForm('edit', scope.row)">编辑</el-button>
            <el-button link type="primary" size="small" @click="openForm('edit', scope.row)" :disabled="!scope.row.isEdit">编辑</el-button>
<!--            <el-button link type="primary" size="small" @click="openForm('view', scope.row)">详情</el-button>-->
            <el-button link type="primary" size="small" @click="downLoadFile(scope.row)">附件</el-button>
<!--            <el-button link type="primary" size="small" @click="openDeliveryForm(scope.row)">发货</el-button>-->
@@ -318,6 +318,15 @@
                    </template>
                </el-table-column>
            </el-table>
            <pagination
                v-show="quotationPage.total > 0"
                :total="quotationPage.total"
                layout="total, sizes, prev, pager, next, jumper"
                :page="quotationPage.current"
                :limit="quotationPage.size"
                @pagination="quotationPaginationChange"
            />
            
            <template #footer>
                <el-button @click="quotationDialogVisible = false">关闭</el-button>
@@ -781,6 +790,12 @@
    quotationNo: "",
    customer: "",
});
// æŠ¥ä»·å•弹框分页
const quotationPage = reactive({
    current: 1,
    size: 10,
    total: 0,
});
const selectedQuotation = ref(null);
// å‘货相关
@@ -1075,6 +1090,8 @@
const openQuotationDialog = async () => {
    if (operationType.value === "view") return;
    quotationDialogVisible.value = true;
    // æ‰“开弹窗时重置分页到第一页
    quotationPage.current = 1;
    // å…ˆç¡®ä¿å®¢æˆ·åˆ—表已加载,便于后续回填 customerId
    if (!customerOption.value || customerOption.value.length === 0) {
        try {
@@ -1091,14 +1108,15 @@
    quotationLoading.value = true;
    try {
        const params = {
            // å…¼å®¹åŽç«¯åˆ†é¡µå­—段:这里沿用报价页面已有可用的字段命名
            currentPage: 1,
            pageSize: 100,
            // åŽç«¯åˆ†é¡µå­—段:current / size
            current: quotationPage.current,
            size: quotationPage.size,
            ...quotationSearchForm,
            status: "通过",
        };
        const res = await getQuotationList(params);
        quotationList.value = res?.data?.records || [];
        quotationPage.total = res?.data?.total || 0;
    } finally {
        quotationLoading.value = false;
    }
@@ -1107,9 +1125,17 @@
const resetQuotationSearch = async () => {
    quotationSearchForm.quotationNo = "";
    quotationSearchForm.customer = "";
    quotationPage.current = 1;
    await fetchQuotationList();
};
// æŠ¥ä»·å•弹框分页切换
const quotationPaginationChange = (obj) => {
    quotationPage.current = obj.page;
    quotationPage.size = obj.limit;
    fetchQuotationList();
};
// é€‰ä¸­æŠ¥ä»·å•后回填到台账表单
const applyQuotation = (row) => {
    if (!row) return;
src/views/salesManagement/salesQuotation/index.vue
@@ -453,15 +453,6 @@
// è®¡ç®—属性
const filteredList = computed(() => {
  let list = quotationList.value
  if (searchForm.quotationNo) {
    list = list.filter(item => item.quotationNo.includes(searchForm.quotationNo))
  }
  if (searchForm.customer) {
    list = list.filter(item => item.customer === searchForm.customer)
  }
  if (searchForm.status) {
    list = list.filter(item => item.status === searchForm.status)
  }
  return list
})
@@ -480,6 +471,9 @@
  searchForm.quotationNo = ''
  searchForm.customer = ''
  searchForm.status = ''
  // é‡ç½®åˆ°ç¬¬ä¸€é¡µå¹¶é‡æ–°æŸ¥è¯¢
  pagination.currentPage = 1
  handleSearch()
}
const handleAdd = async () => {
@@ -848,10 +842,14 @@
const handleCurrentChange = (val) => {
  pagination.currentPage = val.page
  pagination.pageSize = val.limit
  // åˆ†é¡µå˜åŒ–时重新查询列表
  handleSearch()
}
const handleSearch = ()=>{
  const params = {
    ...pagination,
    // åŽç«¯åˆ†é¡µå‚数:current / size
    current: pagination.currentPage,
    size: pagination.pageSize,
    ...searchForm
  }
  getQuotationList(params).then(res=>{