gaoluyang
8 天以前 470d16803de7533c2fa84663e12968d3126dd14f
1.设备台账开发联调
已修改2个文件
已添加3个文件
795 ■■■■■ 文件已修改
src/pages.json 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/ledger/detail.vue 408 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/ledger/index.vue 359 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/index.vue 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/static/images/banner/view-background.png 补丁 | 查看 | 原始文档 | blame | 历史
src/pages.json
@@ -292,6 +292,20 @@
        "navigationBarTitleText": "客户拜访",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/equipmentManagement/ledger/index",
      "style": {
        "navigationBarTitleText": "设备台账",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/equipmentManagement/ledger/detail",
        "style": {
          "navigationBarTitleText": "设备台账详情",
          "navigationStyle": "custom"
        }
    }
  ],
  "subPackages": [
src/pages/equipmentManagement/ledger/detail.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,408 @@
<template>
    <view class="ledger-detail">
        <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
        <PageHeader :title="operationType === 'edit' ? '编辑设备台账' : '新增设备台账'" @back="goBack" />
        <!-- è¡¨å•内容 -->
        <van-form @submit="sendForm" ref="formRef" label-width="110px" input-align="right" error-message-align="right" scroll-to-error scroll-to-error-position="center">
            <!-- åŸºæœ¬ä¿¡æ¯ -->
            <van-cell-group title="基本信息" inset>
                <van-field
                    v-model="form.deviceName"
                    label="设备名称"
                    placeholder="请输入设备名称"
                    :rules="formRules.deviceName"
                    required
                    clearable
                />
                <van-field
                    v-model="form.deviceModel"
                    label="规格型号"
                    placeholder="请输入规格型号"
                    :readonly="form.deviceModel != null && operationType === 'edit'"
                    :rules="formRules.deviceModel"
                    required
                    clearable
                />
                <van-field
                    v-model="form.supplierName"
                    label="供应商"
                    required
                    placeholder="请输入供应商"
                    :rules="formRules.supplierName"
                    clearable
                />
                <van-field
                    v-model="form.unit"
                    label="单位"
                    required
                    placeholder="请输入单位"
                    :rules="formRules.unit"
                    clearable
                />
                <van-field
                    v-model="form.taxRate"
                    required
                    label="税率(%)"
                    placeholder="请选择"
                    readonly
                    :rules="formRules.taxRate"
                    @click="showTaxRatePicker"
                    clearable
                />
                <van-field
                    v-model="form.number"
                    label="数量"
                    required
                    type="number"
                    placeholder="请输入数量"
                    :rules="formRules.number"
                    @blur="mathNum"
                    clearable
                />
                <van-field
                    v-model="form.taxIncludingPriceUnit"
                    label="含税单价"
                    required
                    type="number"
                    placeholder="请输入含税单价"
                    :rules="formRules.taxIncludingPriceUnit"
                    @blur="mathNum"
                    clearable
                />
                <van-field
                    v-model="form.taxIncludingPriceTotal"
                    label="含税总价"
                    placeholder="自动生成"
                    readonly
                />
                <van-field
                    v-model="form.unTaxIncludingPriceTotal"
                    label="不含税总价"
                    placeholder="自动生成"
                    readonly
                />
                <van-field
                    v-model="form.createTime"
                    label="录入日期"
                    placeholder="请选择"
                    readonly
                    @click="showDatePicker"
                    required
                    clearable
                />
            </van-cell-group>
            <!-- æäº¤æŒ‰é’® -->
            <view class="footer-btns">
                <van-button class="cancel-btn" @click="goBack">取消</van-button>
                <van-button class="save-btn" native-type="submit" form-type="submit" :loading="loading">保存</van-button>
            </view>
        </van-form>
        <!-- ç¨ŽçŽ‡é€‰æ‹©å™¨ -->
        <van-popup v-model:show="showTaxRate" position="bottom">
            <van-picker
                :model-value="taxRatePickerValue"
                :columns="taxRateOptions"
                @confirm="onTaxRateConfirm"
                @cancel="showTaxRate = false"
            />
        </van-popup>
        <!-- æ—¥æœŸé€‰æ‹©å™¨ -->
        <van-popup v-model:show="showDate" position="bottom">
            <van-date-picker
                v-model="currentDate"
                title="选择日期"
                @confirm="onDateConfirm"
                @cancel="showDate = false"
            />
        </van-popup>
    </view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { onShow } from '@dcloudio/uni-app';
import PageHeader from '@/components/PageHeader.vue';
import { getLedgerById, addLedger, editLedger } from '@/api/equipmentManagement/ledger';
import dayjs from "dayjs";
import {
    calculateTaxIncludeTotalPrice,
    calculateTaxExclusiveTotalPrice,
} from "@/utils/summarizeTable";
import { showToast } from 'vant';
defineOptions({
    name: "设备台账表单",
});
// è¡¨å•引用
const formRef = ref(null);
const operationType = ref('');
const loading = ref(false);
const showTaxRate = ref(false);
const taxRatePickerValue = ref([]);
const showDate = ref(false);
const currentDate = ref([new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()]);
// è¡¨å•验证规则
const formRules = {
    deviceName: [{ required: true, trigger: "blur", message: "请输入" }],
    deviceModel: [{ required: true, trigger: "blur", message: "请输入" }],
    supplierName: [{ required: true, trigger: "blur", message: "请输入" }],
    unit: [{ required: true, trigger: "blur", message: "请输入" }],
    number: [{ required: true, trigger: "blur", message: "请输入" }],
    taxIncludingPriceUnit: [{ required: true, trigger: "blur", message: "请输入" }],
    taxRate: [{ required: true, trigger: "change", message: "请输入" }],
};
// ä½¿ç”¨ ref å£°æ˜Žè¡¨å•数据
const form = ref({
    deviceName: undefined, // è®¾å¤‡åç§°
    deviceModel: undefined, // è§„格型号
    supplierName: undefined, // ä¾›åº”商
    unit: undefined, // å•位
    number: undefined, // æ•°é‡
    taxIncludingPriceUnit: undefined, // å«ç¨Žå•ä»·
    taxIncludingPriceTotal: undefined, // å«ç¨Žæ€»ä»·
    taxRate: undefined, // ç¨Žçއ
    unTaxIncludingPriceTotal: undefined, // ä¸å«ç¨Žæ€»ä»·
    createTime: dayjs().format("YYYY-MM-DD"), // å½•入日期
});
// ç¨Žçއ选项
const taxRateOptions = computed(() => {
    return [
        { text: '1', value: 1 },
        { text: '6', value: 6 },
        { text: '13', value: 13 }
    ]
});
// åŠ è½½è¡¨å•æ•°æ®
const loadForm = async (id) => {
    if (id) {
        operationType.value = 'edit';
    }
    try {
        const { code, data } = await getLedgerById(id);
        if (code == 200) {
            form.value.deviceName = data.deviceName;
            form.value.deviceModel = data.deviceModel;
            form.value.supplierName = data.supplierName;
            form.value.unit = data.unit;
            form.value.number = data.number;
            form.value.taxIncludingPriceUnit = data.taxIncludingPriceUnit;
            form.value.taxIncludingPriceTotal = data.taxIncludingPriceTotal;
            form.value.taxRate = data.taxRate;
            form.value.unTaxIncludingPriceTotal = data.unTaxIncludingPriceTotal;
            form.value.createTime = data.createTime;
        }
    } catch (e) {
        showToast('获取详情失败');
    }
};
// æ•°å­¦è®¡ç®—
const mathNum = () => {
    if (!form.value.taxIncludingPriceUnit) {
        showToast("请输入单价");
        return;
    }
    if (!form.value.number) {
        showToast("请输入数量");
        return;
    }
    form.value.taxIncludingPriceTotal = calculateTaxIncludeTotalPrice(
        form.value.taxIncludingPriceUnit,
        form.value.number
    );
    if (form.value.taxRate) {
        form.value.unTaxIncludingPriceTotal = calculateTaxExclusiveTotalPrice(
            form.value.taxIncludingPriceTotal,
            form.value.taxRate
        );
    }
};
// æ¸…除表单校验状态
const clearValidate = () => {
    formRef.value?.clearValidate();
};
// é‡ç½®è¡¨å•数据和校验状态
const resetForm = () => {
    form.value = {
        deviceName: undefined,
        deviceModel: undefined,
        supplierName: undefined,
        unit: undefined,
        number: undefined,
        taxIncludingPriceUnit: undefined,
        taxIncludingPriceTotal: undefined,
        taxRate: undefined,
        unTaxIncludingPriceTotal: undefined,
        createTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
    };
};
const resetFormAndValidate = () => {
    resetForm();
    clearValidate();
};
// æäº¤è¡¨å•
const sendForm = async () => {
    try {
        // æ‰‹åŠ¨éªŒè¯è¡¨å•
        await formRef.value?.validate();
        loading.value = true;
        const id = getPageId();
        // å‡†å¤‡æäº¤æ•°æ®ï¼ŒcreateTime åŠ ä¸Šå½“å‰æ—¶åˆ†ç§’
        const submitData = { ...form.value };
        if (submitData.createTime && !submitData.createTime.includes(':')) {
            // å¦‚æžœ createTime åªåŒ…含日期,添加当前时分秒
            submitData.createTime = submitData.createTime + ' ' + dayjs().format('HH:mm:ss');
        }
        const { code } = id
            ? await editLedger({ id: id, ...submitData })
            : await addLedger(submitData);
        if (code == 200) {
            showToast("操作成功");
            setTimeout(() => {
                uni.navigateBack();
            }, 1500);
        } else {
            loading.value = false;
        }
    } catch (e) {
        loading.value = false;
        showToast('表单验证失败');
    }
};
// è¿”回上一页
const goBack = () => {
    uni.navigateBack();
};
// èŽ·å–é¡µé¢å‚æ•°
const getPageParams = () => {
    const pages = getCurrentPages();
    const currentPage = pages[pages.length - 1];
    const options = currentPage.options;
    if (options.id) {
        // ç¼–辑模式,获取详情
        loadForm(options.id);
    } else {
        // æ–°å¢žæ¨¡å¼
        operationType.value = 'add';
    }
};
// èŽ·å–é¡µé¢ID
const getPageId = () => {
    const pages = getCurrentPages();
    const currentPage = pages[pages.length - 1];
    const options = currentPage.options;
    return options.id;
};
// æ˜¾ç¤ºç¨ŽçŽ‡é€‰æ‹©å™¨
const showTaxRatePicker = () => {
    showTaxRate.value = true;
};
// ç¡®è®¤ç¨ŽçŽ‡é€‰æ‹©
const onTaxRateConfirm = ({ selectedValues, selectedOptions }) => {
    form.value.taxRate = selectedOptions[0].value;
    taxRatePickerValue.value = selectedValues;
    showTaxRate.value = false;
    mathNum(); // é‡æ–°è®¡ç®—
};
// æ˜¾ç¤ºæ—¥æœŸé€‰æ‹©å™¨
const showDatePicker = () => {
    showDate.value = true;
};
// ç¡®è®¤æ—¥æœŸé€‰æ‹©
const onDateConfirm = ({ selectedValues }) => {
    // åªä¿å­˜å¹´æœˆæ—¥ï¼Œä¸åŒ…含时分秒
    form.value.createTime = selectedValues.join('-');
    currentDate.value = selectedValues;
    showDate.value = false;
};
onShow(() => {
    // é¡µé¢æ˜¾ç¤ºæ—¶èŽ·å–å‚æ•°
    getPageParams();
});
onMounted(() => {
    // é¡µé¢åŠ è½½æ—¶èŽ·å–å‚æ•°
    getPageParams();
});
</script>
<style scoped lang="scss">
.ledger-detail {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 5rem;
}
.footer-btns {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    background: #fff;
    display: flex;
    justify-content: space-around;
    align-items: center;
    padding: 0.75rem 0;
    box-shadow: 0 -0.125rem 0.5rem rgba(0,0,0,0.05);
    z-index: 1000;
}
.cancel-btn {
    font-weight: 400;
    font-size: 1rem;
    color: #FFFFFF;
    width: 6.375rem;
    background: #C7C9CC;
    box-shadow: 0 0.25rem 0.625rem 0 rgba(3,88,185,0.2);
    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
}
.save-btn {
    font-weight: 400;
    font-size: 1rem;
    color: #FFFFFF;
    width: 14rem;
    background: linear-gradient( 140deg, #00BAFF 0%, #006CFB 100%);
    box-shadow: 0 0.25rem 0.625rem 0 rgba(3,88,185,0.2);
    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
}
// å“åº”式调整
@media (max-width: 768px) {
    .submit-section {
        padding: 12px;
    }
}
.tip-text {
    padding: 4px 16px 0 16px;
    font-size: 12px;
    color: #888;
}
</style>
src/pages/equipmentManagement/ledger/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,359 @@
<template>
  <view class="device-ledger">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="设备台账" @back="goBack" />
    <!-- æœç´¢å’Œç­›é€‰åŒºåŸŸ -->
    <view class="search-filter-section">
      <view class="search-bar">
        <view class="search-input">
          <input
            class="search-text"
            placeholder="请输入设备名称"
            v-model="searchKeyword"
            confirm-type="search"
            @confirm="getList"
          />
        </view>
        <view class="filter-button" @click="getList">
          <up-icon name="search" size="24" color="#999"></up-icon>
        </view>
      </view>
    </view>
    <!-- è®¾å¤‡å°è´¦åˆ—表 -->
    <view class="ledger-list" v-if="ledgerList.length > 0">
      <view v-for="(item, index) in ledgerList" :key="index">
        <view class="ledger-item">
          <view class="item-header">
            <view class="item-left">
              <view class="document-icon">
                <up-icon name="file-text" size="16" color="#ffffff"></up-icon>
              </view>
              <text class="item-id">设备名称:{{ item.deviceName }}</text>
            </view>
          </view>
          <up-divider></up-divider>
          <view class="item-details">
            <view class="detail-row">
              <text class="detail-label">规格型号</text>
              <text class="detail-value">{{ item.deviceModel || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">供应商</text>
              <text class="detail-value">{{ item.supplierName || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">单位</text>
              <text class="detail-value">{{ item.unit || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">数量</text>
              <text class="detail-value">{{ item.number || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">含税单价</text>
              <text class="detail-value highlight">{{ item.taxIncludingPriceUnit || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">含税总价</text>
              <text class="detail-value highlight">{{ item.taxIncludingPriceTotal || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">税率</text>
              <text class="detail-value">{{ item.taxRate || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">不含税总价</text>
              <text class="detail-value highlight">{{ item.unTaxIncludingPriceTotal || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">录入人</text>
              <text class="detail-value">{{ item.createUser || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">录入日期</text>
              <text class="detail-value">{{ item.createTime || '-' }}</text>
            </view>
          </view>
          <!-- æŒ‰é’®åŒºåŸŸï¼Œå‚考 invoiceLedger çš„æ ·å¼ -->
          <view class="action-buttons">
            <van-button
              type="primary"
              size="small"
              class="action-btn"
              @click="edit(item.id)"
            >
              ç¼–辑
            </van-button>
            <van-button
              type="danger"
              size="small"
              plain
              class="action-btn"
              @click="deleteRow(item.id)"
            >
              åˆ é™¤
            </van-button>
          </view>
        </view>
      </view>
    </view>
    <view v-else class="no-data">
      <text>暂无设备台账数据</text>
    </view>
    <!-- æµ®åŠ¨æ–°å¢žæŒ‰é’® -->
    <view class="fab-button" @click="add">
      <up-icon name="plus" size="24" color="#ffffff"></up-icon>
    </view>
  </view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import PageHeader from '@/components/PageHeader.vue'
import { getLedgerPage, delLedger } from '@/api/equipmentManagement/ledger'
import useUserStore from "@/store/modules/user"
import { showToast } from 'vant';
const userStore = useUserStore()
// æœç´¢å…³é”®è¯
const searchKeyword = ref('')
// è®¾å¤‡å°è´¦æ•°æ®
const ledgerList = ref([])
// è¿”回上一页
const goBack = () => {
  uni.navigateBack()
}
// æŸ¥è¯¢åˆ—表(current/size å›ºå®šä¼  -1)
const getList = () => {
  const params = {
    current: -1,
    size: -1,
    deviceName: searchKeyword.value || undefined,
  }
  getLedgerPage(params)
    .then((res) => {
      ledgerList.value = res.records || res.data?.records || []
    })
    .catch(() => {
      showToast('获取数据失败')
    })
}
// æ–°å¢ž - è·³è½¬åˆ°è¯¦æƒ…页面
const add = () => {
  uni.navigateTo({
    url: '/pages/equipmentManagement/ledger/detail'
  })
}
// ç¼–辑 - è·³è½¬åˆ°è¯¦æƒ…页面
const edit = (id) => {
  if (!id) return
  uni.navigateTo({
    url: `/pages/equipmentManagement/ledger/detail?id=${id}`
  })
}
// åˆ é™¤
const deleteRow = async (id) => {
  if (!id) return
  uni.showModal({
    title: '提示',
    content: '此操作将永久删除该记录, æ˜¯å¦ç»§ç»­?',
    success: async (res) => {
      if (!res.confirm) return
      try {
        await delLedger(id)
        showToast('删除成功')
        getList()
      } catch (e) {
        showToast('删除失败')
      }
    }
  })
}
onMounted(() => {
  getList()
})
onShow(() => {
  getList()
})
</script>
<style scoped lang="scss">
.u-divider {
  margin: 0 !important;
}
.device-ledger {
  min-height: 100vh;
  background: #f8f9fa;
  position: relative;
  padding-bottom: 80px;
}
.search-filter-section {
  padding: 10px 20px;
  background: #ffffff;
}
.search-bar {
  display: flex;
  align-items: center;
  gap: 12px;
}
.search-input {
  flex: 1;
  background: #f5f5f5;
  border-radius: 24px;
  padding: 10px 16px;
  display: flex;
  align-items: center;
  gap: 8px;
}
.search-text {
  flex: 1;
  font-size: 14px;
  color: #333;
  background: transparent;
  border: none;
  outline: none;
}
.search-text::placeholder {
  color: #999;
}
.filter-button {
  width: 40px;
  height: 40px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.ledger-list {
  padding: 20px;
}
.ledger-item {
  background: #ffffff;
  border-radius: 12px;
  margin-bottom: 16px;
  overflow: hidden;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
  padding: 0 16px;
}
.item-header {
  padding: 16px 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.item-left {
  display: flex;
  align-items: center;
  gap: 8px;
}
.document-icon {
  width: 24px;
  height: 24px;
  background: #2979ff;
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.item-id {
  font-size: 14px;
  color: #333;
  font-weight: 500;
}
.item-details {
  padding: 16px 0;
}
.detail-row {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  margin-bottom: 8px;
  &:last-child {
    margin-bottom: 0;
  }
}
.detail-label {
  font-size: 12px;
  color: #777777;
  min-width: 60px;
}
.detail-value {
  font-size: 12px;
  color: #000000;
  text-align: right;
  flex: 1;
  margin-left: 16px;
}
.detail-value.highlight {
  color: #2979ff;
  font-weight: 500;
}
.no-data {
  padding: 40px 0;
  text-align: center;
  color: #999;
}
// æŒ‰é’®æ ·å¼ï¼Œå‚考 invoiceLedger
.action-buttons {
  display: flex;
  gap: 12px;
  padding: 0 0 16px 0;
  justify-content: space-between;
}
.action-btn {
  flex: 1;
}
.fab-button {
  position: fixed;
  bottom: calc(30px + env(safe-area-inset-bottom));
  right: 30px;
  width: 56px;
  height: 56px;
  background: #2979ff;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 4px 16px rgba(41, 121, 255, 0.3);
  z-index: 1000;
}
</style>
src/pages/index.vue
@@ -11,8 +11,8 @@
        <view class="hero-section">
            <view class="bg-img">
                <view class="hero-content">
                    <text class="hero-title">产品库存管理系统</text>
                    <text class="hero-subtitle">高效、便捷的业务管理入口</text>
                    <text class="hero-title"></text>
                    <text class="hero-subtitle"></text>
                </view>
                <view class="hero-wave"></view>
            </view>
@@ -355,9 +355,14 @@
                url: '/pages/cooperativeOffice/collaborativeApproval/index'
            });
            break;
                    case '客户拜访':
        case '客户拜访':
            uni.navigateTo({
                url: '/pages/cooperativeOffice/clientVisit/index'
            });
            break;
        case '设备台账':
            uni.navigateTo({
                url: '/pages/equipmentManagement/ledger/index'
            });
            break;
        default:
@@ -494,7 +499,8 @@
.bg-img {
    width: 100%;
    height: 8.75rem;
    background: linear-gradient(135deg, #2979ff 0%, #1565c0 100%);
    background-image: url("../static/images/banner/view-background.png");
    background-size: cover;
    border-radius: 0.75rem;
    position: relative;
    overflow: hidden;
src/static/images/banner/view-background.png