| | |
| | | // 应用全局配置 |
| | | const config = { |
| | | baseUrl: "http://1.15.17.182:9048", |
| | | fileUrl: "http://1.15.17.182:9049", |
| | | baseUrl: "http://42.63.71.140:9000", |
| | | fileUrl: "http://42.63.71.140:9001", |
| | | // 应用信息 |
| | | appInfo: { |
| | | // 应用名称 |
| | |
| | | children: [ |
| | | // { label: "员工档案", icon: "/static/images/icon/renyuanxinzi.svg", path: OA_NAV.staffArchive }, |
| | | // { label: "员工合同", icon: "/static/images/icon/hetongguanli.svg", path: OA_NAV.staffContract }, |
| | | { label: "转正申请", icon: "/static/images/icon/hetongguanli.svg", path: OA_NAV.regularApply }, |
| | | { label: "调岗申请", icon: "/static/images/icon/renyuanxinzi.svg", path: OA_NAV.transferApply }, |
| | | // { label: "转正申请", icon: "/static/images/icon/hetongguanli.svg", path: OA_NAV.regularApply }, |
| | | // { label: "调岗申请", icon: "/static/images/icon/renyuanxinzi.svg", path: OA_NAV.transferApply }, |
| | | // { label: "离职申请", icon: "/static/images/icon/qingjiaguanli.svg", path: OA_NAV.resignApply }, |
| | | { label: "工作交接", icon: "/static/images/icon/gongchuguanli.svg", path: OA_NAV.workHandover }, |
| | | // { label: "工作交接", icon: "/static/images/icon/gongchuguanli.svg", path: OA_NAV.workHandover }, |
| | | // { label: "岗位管理", icon: "/static/images/icon/gongxuguanli.svg", path: OA_NAV.postManage }, |
| | | ], |
| | | }, |
| | |
| | | ...item, |
| | | module: module.name, |
| | | moduleKey: module.key, |
| | | })) |
| | | })), |
| | | ); |
| | |
| | | { |
| | | "name" : "信息管理Pro-OA", |
| | | "appid" : "__UNI__751174B", |
| | | "name" : "宁夏万通", |
| | | "appid" : "__UNI__DBB6066", |
| | | "description" : "", |
| | | "versionName" : "1.1.5", |
| | | "versionCode" : 100, |
| | |
| | | } else { |
| | | noNeedCheckIn.value = false; |
| | | } |
| | | updateInCheckRange(); |
| | | } else { |
| | | // 页面显示“无需打卡” |
| | | todayRecord.value = {}; |
| | | noNeedCheckIn.value = true; |
| | | updateInCheckRange(); |
| | | } |
| | | }); |
| | | }; |
| | |
| | | const m = String(now.getMinutes()).padStart(2, "0"); |
| | | const s = String(now.getSeconds()).padStart(2, "0"); |
| | | nowTime.value = `${Y}-${M}-${D} ${h}:${m}:${s}`; |
| | | updateInCheckRange(); |
| | | }; |
| | | |
| | | // 今日日期 |
| | | const todayStr = computed(() => nowTime.value.slice(0, 10)); |
| | | |
| | | const parseHmToMinutes = hm => { |
| | | if (!hm || typeof hm !== "string") return null; |
| | | const [hStr, mStr] = hm.split(":"); |
| | | const h = Number(hStr); |
| | | const m = Number(mStr); |
| | | if (!Number.isFinite(h) || !Number.isFinite(m)) return null; |
| | | if (h < 0 || h > 23 || m < 0 || m > 59) return null; |
| | | return h * 60 + m; |
| | | }; |
| | | |
| | | const parseYmdToDate = ymd => { |
| | | if (!ymd || typeof ymd !== "string") return null; |
| | | const [yStr, mStr, dStr] = ymd.slice(0, 10).split("-"); |
| | | const y = Number(yStr); |
| | | const m = Number(mStr); |
| | | const d = Number(dStr); |
| | | if (!Number.isFinite(y) || !Number.isFinite(m) || !Number.isFinite(d)) { |
| | | return null; |
| | | } |
| | | return new Date(y, m - 1, d, 0, 0, 0, 0); |
| | | }; |
| | | |
| | | const addMinutes = (date, minutes) => { |
| | | return new Date(date.getTime() + minutes * 60 * 1000); |
| | | }; |
| | | |
| | | const buildShiftWindow = () => { |
| | | const startAtMinutes = parseHmToMinutes(todayRecord.value?.startAt); |
| | | const endAtMinutes = parseHmToMinutes(todayRecord.value?.endAt); |
| | | if (startAtMinutes === null || endAtMinutes === null) return null; |
| | | |
| | | const baseYmd = todayRecord.value?.date |
| | | ? String(todayRecord.value.date).slice(0, 10) |
| | | : todayStr.value; |
| | | const baseDate = parseYmdToDate(baseYmd); |
| | | if (!baseDate) return null; |
| | | |
| | | const startDateTime = addMinutes(baseDate, startAtMinutes); |
| | | const crossDay = startAtMinutes > endAtMinutes; |
| | | const endBase = crossDay ? addMinutes(baseDate, 24 * 60) : baseDate; |
| | | const endDateTime = addMinutes(endBase, endAtMinutes); |
| | | |
| | | return { startDateTime, endDateTime }; |
| | | }; |
| | | |
| | | const updateInCheckRange = () => { |
| | | if (noNeedCheckIn.value) { |
| | | inCheckRange.value = true; |
| | | return; |
| | | } |
| | | const window = buildShiftWindow(); |
| | | if (!window) { |
| | | inCheckRange.value = true; |
| | | return; |
| | | } |
| | | const now = new Date(); |
| | | const checkInEarlyMinutes = 120; |
| | | const checkOutLateMinutes = 720; |
| | | const needAction = (() => { |
| | | if (todayRecord.value?.workEndAt) return "done"; |
| | | if (todayRecord.value?.workStartAt) return "checkOut"; |
| | | const distToStart = Math.abs( |
| | | now.getTime() - window.startDateTime.getTime() |
| | | ); |
| | | const distToEnd = Math.abs(now.getTime() - window.endDateTime.getTime()); |
| | | return distToEnd < distToStart ? "checkOut" : "checkIn"; |
| | | })(); |
| | | const start = addMinutes(window.startDateTime, -checkInEarlyMinutes); |
| | | const end = addMinutes( |
| | | window.endDateTime, |
| | | needAction === "checkOut" ? checkOutLateMinutes : 0 |
| | | ); |
| | | inCheckRange.value = now >= start && now <= end; |
| | | }; |
| | | |
| | | // 打卡按钮文本 |
| | | const checkInOutText = computed(() => { |
| | | if (noNeedCheckIn.value) { |
| | | return "无需打卡"; |
| | | } |
| | | if (!todayRecord.value || !todayRecord.value.workStartAt) { |
| | | return "上班打卡"; |
| | | } |
| | | if (!todayRecord.value.workEndAt) { |
| | | return "下班打卡"; |
| | | if (todayRecord.value.workStartAt) { |
| | | return "下班打卡"; |
| | | } |
| | | const window = buildShiftWindow(); |
| | | if (!window) return "上班打卡"; |
| | | const now = new Date(); |
| | | const distToStart = Math.abs( |
| | | now.getTime() - window.startDateTime.getTime() |
| | | ); |
| | | const distToEnd = Math.abs(now.getTime() - window.endDateTime.getTime()); |
| | | return distToEnd < distToStart ? "下班打卡" : "上班打卡"; |
| | | } |
| | | return "已打卡"; |
| | | }); |
| | |
| | | .attendance-records { |
| | | animation-delay: 0.2s; |
| | | } |
| | | </style> |
| | | </style> |
| | |
| | | :disabled="isReadOnly" |
| | | placeholder="请输入付款方式" /> |
| | | </up-form-item> |
| | | <up-form-item label="车牌号" |
| | | prop="carPlateNumber"> |
| | | <up-input v-model="form.carPlateNumber" |
| | | :disabled="isReadOnly" |
| | | placeholder="请输入车牌号" /> |
| | | </up-form-item> |
| | | <up-form-item label="运输单位/个人" |
| | | prop="transportUnitOrPerson"> |
| | | <up-input v-model="form.transportUnitOrPerson" |
| | | :disabled="isReadOnly" |
| | | placeholder="请输入运输单位/个人" /> |
| | | </up-form-item> |
| | | <up-form-item label="签订日期" |
| | | required |
| | | prop="executionDate"> |
| | |
| | | placeholder="请输入" |
| | | @blur="formatAmount(idx)" /> |
| | | </up-form-item> |
| | | <up-form-item label="运费单价(元)" |
| | | prop="freightUnitPrice" |
| | | :rules="productRules"> |
| | | <up-input v-model="product.freightUnitPrice" |
| | | type="number" |
| | | :disabled="!canEditProducts" |
| | | placeholder="请输入" |
| | | @blur="formatFreightUnitPrice(idx)" /> |
| | | </up-form-item> |
| | | <up-form-item label="总运费(元)" |
| | | prop="totalFreight" |
| | | :rules="productRules"> |
| | | <up-input v-model="product.totalFreight" |
| | | type="number" |
| | | :disabled="!canEditProducts" |
| | | placeholder="请输入" |
| | | @blur="formatTotalFreight(idx)" /> |
| | | </up-form-item> |
| | | <!-- 含税总价 --> |
| | | <up-form-item label="含税总价(元)" |
| | | prop="taxInclusiveTotalPrice" |
| | |
| | | supplierName: "", |
| | | projectName: "", |
| | | paymentMethod: "", |
| | | carPlateNumber: "", |
| | | transportUnitOrPerson: "", |
| | | recorderId: "", |
| | | recorderName: "", |
| | | entryDate: "", |
| | |
| | | taxRate: "", |
| | | taxInclusiveUnitPrice: "", |
| | | quantity: "", |
| | | freightUnitPrice: "", |
| | | totalFreight: "", |
| | | taxInclusiveTotalPrice: "", |
| | | taxExclusiveTotalPrice: "", |
| | | invoiceType: "", |
| | |
| | | } |
| | | }; |
| | | |
| | | const formatFreightUnitPrice = idx => { |
| | | if (productData.value[idx].freightUnitPrice) { |
| | | const value = parseFloat(productData.value[idx].freightUnitPrice); |
| | | if (!isNaN(value)) { |
| | | productData.value[idx].freightUnitPrice = value.toFixed(2); |
| | | } |
| | | } |
| | | const quantity = parseFloat(productData.value[idx].quantity); |
| | | const unitPrice = parseFloat(productData.value[idx].freightUnitPrice); |
| | | if (!quantity || quantity <= 0 || !unitPrice) return; |
| | | productData.value[idx].totalFreight = (unitPrice * quantity).toFixed(2); |
| | | }; |
| | | |
| | | const formatTotalFreight = idx => { |
| | | if (productData.value[idx].totalFreight) { |
| | | const value = parseFloat(productData.value[idx].totalFreight); |
| | | if (!isNaN(value)) { |
| | | productData.value[idx].totalFreight = value.toFixed(2); |
| | | } |
| | | } |
| | | const quantity = parseFloat(productData.value[idx].quantity); |
| | | const totalFreight = parseFloat(productData.value[idx].totalFreight); |
| | | if (!quantity || quantity <= 0 || !totalFreight) return; |
| | | productData.value[idx].freightUnitPrice = (totalFreight / quantity).toFixed( |
| | | 2 |
| | | ); |
| | | }; |
| | | |
| | | // 数量输入框失焦 |
| | | const formatAmount = idx => { |
| | | if (productData.value[idx].quantity) { |
| | |
| | | approverNodes.value = [{ id: 1, userId: null, nickName: null }]; |
| | | nextApproverId = 2; |
| | | } |
| | | |
| | | form.value.carPlateNumber = |
| | | res?.carPlateNumber ?? editData.value?.carPlateNumber ?? ""; |
| | | form.value.transportUnitOrPerson = |
| | | res?.transportUnitOrPerson ?? editData.value?.transportUnitOrPerson ?? ""; |
| | | }); |
| | | console.log(editData.value); |
| | | // 填充基本信息 |
| | |
| | | form.value.id = editData.value.id || ""; |
| | | form.value.supplierId = editData.value.supplierId || ""; |
| | | form.value.executionDate = editData.value.executionDate || ""; |
| | | form.value.carPlateNumber = editData.value.carPlateNumber || ""; |
| | | form.value.transportUnitOrPerson = editData.value.transportUnitOrPerson || ""; |
| | | }; |
| | | |
| | | const getSalesNoList = () => { |
| | |
| | | color: #3b82f6; |
| | | font-size: 14px; |
| | | } |
| | | </style> |
| | | </style> |
| | |
| | | <text class="detail-value">{{ item.taxInclusiveUnitPrice }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">运费单价(元)</text> |
| | | <text class="detail-value">{{ formatMoney2(item.freightUnitPrice) }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">总运费(元)</text> |
| | | <text class="detail-value">{{ formatMoney2(item.totalFreight) }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">含税总价(元)</text> |
| | | <text class="detail-value">{{ item.taxInclusiveTotalPrice }}</text> |
| | | </view> |
| | |
| | | uni.hideLoading(); |
| | | }; |
| | | const outData = ref({}); |
| | | const formatMoney2 = value => { |
| | | if (value === null || value === undefined || value === "") return "-"; |
| | | const num = Number(value); |
| | | if (Number.isNaN(num)) return String(value); |
| | | return num.toFixed(2); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | // 页面加载时获取参数并刷新列表 |
| | |
| | | icon: "/static/images/icon/dakaqiandao.svg", |
| | | label: "打卡签到", |
| | | }, |
| | | { |
| | | icon: "/static/images/icon/renyuanxinzi.svg", |
| | | label: "人员薪资", |
| | | }, |
| | | // { |
| | | // icon: "/static/images/icon/renyuanxinzi.svg", |
| | | // label: "人员薪资", |
| | | // }, |
| | | { |
| | | icon: "/static/images/icon/hetongguanli.svg", |
| | | label: "合同管理", |
| | |
| | | }, |
| | | ]); |
| | | // OA办公功能数据(纯前端配置,不参与后端权限过滤) |
| | | const oaItems = reactive( |
| | | OA_WORKBENCH_ITEMS.map(item => ({ ...item })) |
| | | ); |
| | | const oaItems = reactive(OA_WORKBENCH_ITEMS.map(item => ({ ...item }))); |
| | | |
| | | // 协同办公功能数据 |
| | | const collaborationItems = reactive([ |
| | | { |
| | | icon: "/static/images/icon/xietongshenpi.svg", |
| | | label: "协同审批", |
| | | }, |
| | | // { |
| | | // icon: "/static/images/icon/xietongshenpi.svg", |
| | | // label: "协同审批", |
| | | // }, |
| | | { |
| | | icon: "/static/images/icon/huiyiguanli.svg", |
| | | label: "会议管理", |