gaoluyang
2 天以前 025e46e11cb2962fd7692adfa401333758cc779b
修改组件
已删除2个文件
已修改56个文件
已添加4个文件
31124 ■■■■ 文件已修改
.gitignore 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package-lock.json 11562 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pnpm-lock.yaml 9095 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/App.vue 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/geek-xd/components/geek-qrcode/README.md 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/config.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.js 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages.json 77 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/clientVisit/detail.vue 60 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/clientVisit/index.vue 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/collaborativeApproval/approve.vue 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/collaborativeApproval/contactSelect.vue 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/collaborativeApproval/detail.vue 269 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/collaborativeApproval/index.vue 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/ledger/detail.vue 214 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/repair/add.vue 176 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/repair/index.vue 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/repair/maintain.vue 134 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/upkeep/add.vue 157 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/upkeep/index.vue 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/upkeep/maintain.vue 96 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/index.vue 44 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/login.vue 256 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/procurementManagement/invoiceEntry/add.vue 590 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/procurementManagement/invoiceEntry/index.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/procurementManagement/paymentEntry/add.vue 139 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/procurementManagement/paymentEntry/edit.vue 176 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/procurementManagement/paymentEntry/index.vue 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/procurementManagement/paymentLedger/detail.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/procurementManagement/procurementInvoiceLedger/detail.vue 276 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/procurementManagement/procurementInvoiceLedger/index.vue 312 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/procurementManagement/procurementLedger/detail.vue 917 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/procurementManagement/receiptPaymentHistory/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/invoiceLedger/detail.vue 100 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/invoiceLedger/index.vue 129 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/invoicingRegistration/add.vue 427 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/invoicingRegistration/index.vue 33 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/invoicingRegistration/view.vue 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/receiptPayment/add.vue 140 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/receiptPayment/edit.vue 139 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/receiptPayment/index.vue 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/receiptPaymentHistory/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/receiptPaymentLedger/detail.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/salesAccount/detail.vue 898 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/template.vue 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/work.vue 181 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages_mine/pages/company/index.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages_mine/pages/contract/index.vue 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages_mine/pages/info/index.vue 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages_qiun/components/data-progress/data-progress.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages_qiun/pages/main/index.vue 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages_template/pages/coupon/components/jingdong-coupon.vue 136 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages_template/pages/coupon/components/meituan-coupon.vue 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages_template/pages/coupon/components/taobao-coupon.vue 140 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages_template/pages/coupon/index.vue 402 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages_template/pages/keyboardPay/index.vue 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/static/scss/form-common.scss 270 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/user.ts 96 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/uni_modules/uni-scss/styles/setting/_variables.scss 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/ruoyi.js 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yarn.lock 2715 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.gitignore
@@ -45,4 +45,9 @@
!*.allowed_extension
# ä¸æŽ’除下列文件名
!important_file.txt
!important_file.txt
# å¿½ç•¥ç”±äºŽnode版本不同导致lock文件不同,只需要保留package.json即可
package-lock.json
yarn.lock
pnpm-lock.yaml
package-lock.json
ÎļþÒÑɾ³ý
package.json
@@ -77,8 +77,7 @@
    "mqtt": "4.1.0",
    "pinia": "2.2.2",
    "tslib": "^2.7.0",
    "uview-plus": "^3.5.25",
    "vant": "^4.9.21",
    "uview-plus": "^3.4.62",
    "vue": "3.4.21",
    "vue-i18n": "^9.14.2"
  },
@@ -91,10 +90,10 @@
    "@vue/runtime-core": "^3.5.12",
    "@vue/tsconfig": "^0.5.1",
    "less": "^4.2.0",
    "sass": "^1.78.0",
    "sass": "1.78.0",
    "sass-loader": "^16.0.1",
    "typescript": "^5.6.2",
    "vite": "5.2.8",
    "vite": "5.4.10",
    "vue-tsc": "2.1.6"
  }
}
pnpm-lock.yaml
ÎļþÒÑɾ³ý
src/App.vue
@@ -1,29 +1,22 @@
<template>
  <Splash v-if="showSplash" />
    <Splash v-if="showSplash" />
    <div v-else>
    <router-view />
  </div>
        <router-view />
    </div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import Splash from './components/Splash.vue'
const showSplash = ref(true)
onMounted(() => {
  setTimeout(() => {
    showSplash.value = false
  }, 5000)
    setTimeout(() => {
        showSplash.value = false
    }, 5000)
})
</script>
<style lang="scss">
// ä½¿ç”¨ @use æ›¿ä»£ @import(如果uView-plus支持的话)
// æ³¨æ„ï¼šç”±äºŽuView-plus可能还不支持@use,这可能会导致样式问题
// @use "uview-plus/index.scss";
// @use '@/static/scss/index.scss';
// æš‚时保留@import,直到uView-plus更新
@import "uview-plus/index.scss";
@import '@/static/scss/index.scss';
</style>
src/components/geek-xd/components/geek-qrcode/README.md
@@ -2,25 +2,12 @@
改自作者诗小柒的tki-qrcode二维码生成器
### ä½œè€…:董玉可
### ä½œè€…:Dftre
1. H5、微信小程序、支付宝小程序、APP,其它平台的小程序没有测试
2. ä½¿ç”¨canvas生成
3. å¯è®¾ç½®äºŒç»´ç èƒŒæ™¯è‰²ï¼Œå‰æ™¯è‰²ï¼Œè§’标色
4. å¯è®¾ç½®äºŒç»´ç logo
## é‡è¦çš„事情说3遍 é‡è¦çš„事情说3遍 é‡è¦çš„事情说3遍
1. IOS、Android真机都可以正常生成二维码
2. ä½¿ç”¨çš„æ—¶å€™å‡ºçŽ°æ— æ³•ç”ŸæˆäºŒç»´ç æˆ–ç©ºç™½çš„è¯·å…ˆgithub直接打包下载,问题依旧,请github上直接提出问题并配图
3. æœ‰é—®é¢˜è¯·è¯´æ˜Žé—®é¢˜åŽŸå› ï¼Œè¿™æ ·æˆ‘æ‰å¥½å®šä½ï¼Œå¦åˆ™æˆ‘ä¹Ÿæ— æ³•è§£å†³
4. å¦‚果此插件有帮助到你请打5分或赞赏我,你的支持是我更新的动力
+ å›¾ç‰‡1 æ˜¯å¾®ä¿¡å°ç¨‹åºçœŸæœºå®žæµ‹
+ å›¾ç‰‡2 æ˜¯å¾®ä¿¡å°ç¨‹åºæ¨¡æ‹Ÿå®žæµ‹
+ å›¾ç‰‡3 æ˜¯æ”¯ä»˜å®å°ç¨‹åºæ¨¡æ‹Ÿå™¨å®žæµ‹
+ å›¾ç‰‡4 æ˜¯å®‰å“真机实测
+ å›¾ç‰‡5 H5
### ä½¿ç”¨æ–¹æ³•
@@ -88,3 +75,4 @@
[uni-app](https://uniapp.dcloud.io/ "uni-app")
[qrcode](https://github.com/aralejs/qrcode "qrcode")
tki-qrcode
src/config.js
@@ -1,7 +1,8 @@
// åº”用全局配置
const config = {
  baseUrl: 'http://114.132.189.42:8089', // æµ‹è¯•库
  // baseUrl: 'http://192.168.1.147:7003', // æœ¬åœ°è”è°ƒ
  //  baseUrl: 'https://vue.ruoyi.vip/prod-api',
  // baseUrl: 'http://localhost/prod-api',
  baseUrl: 'http://114.132.189.42:8089',
   //cloud后台网关地址
  //  baseUrl: 'http://192.168.10.3:8080',
   // åº”用信息
src/main.js
@@ -9,11 +9,10 @@
import directive from './directive' // directive
import { useDict } from '@/utils/dict'
import { parseTime, resetForm, addDateRange, handleTree, selectDictLabel, selectDictLabels } from '@/utils/ruoyi'
import { parseTime, resetForm, addDateRange, handleTree, selectDictLabel, selectDictLabels, formatDateToYMD } from '@/utils/ruoyi'
import {
  calculateTaxExclusiveTotalPrice,
} from "@/utils/summarizeTable.js";
export function createApp() {
@@ -21,9 +20,6 @@
  app.use(store)
  app.use(uviewPlus)
  app.use(plugins)
  // ç§»é™¤ä»¥ä¸‹è¡Œ Vant æ³¨å†Œ
  // app.use(Vant)
  // æ³¨å†Œå…¨å±€ç»„ä»¶
  setupGlobalComponents(app)
@@ -41,6 +37,7 @@
  app.config.globalProperties.selectDictLabel = selectDictLabel
  app.config.globalProperties.selectDictLabels = selectDictLabels
  app.config.globalProperties.calculateTaxExclusiveTotalPrice = calculateTaxExclusiveTotalPrice;
  app.config.globalProperties.formatDateToYMD = formatDateToYMD;
  return {
    app
src/pages.json
@@ -95,7 +95,7 @@
    {
      "path": "pages/sales/invoiceLedger/index",
      "style": {
        "navigationBarTitleText": "",
        "navigationBarTitleText": "开票台账",
        "navigationStyle": "custom"
      }
    },
@@ -247,18 +247,6 @@
      }
    },
    {
      "path": "pages/common/webview/index",
      "style": {
        "navigationBarTitleText": "浏览网页"
      }
    },
    {
      "path": "pages/common/textview/index",
      "style": {
        "navigationBarTitleText": "浏览文本"
      }
    },
    {
      "path": "pages/cooperativeOffice/collaborativeApproval/index",
      "style": {
        "navigationBarTitleText": "审批管理",
@@ -294,6 +282,13 @@
      }
    },
    {
      "path": "pages/cooperativeOffice/clientVisit/detail",
      "style": {
        "navigationBarTitleText": "添加客户拜访",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/equipmentManagement/ledger/index",
      "style": {
        "navigationBarTitleText": "设备台账",
@@ -302,52 +297,52 @@
    },
    {
      "path": "pages/equipmentManagement/ledger/detail",
        "style": {
          "navigationBarTitleText": "设备台账详情",
          "navigationStyle": "custom"
        }
      "style": {
        "navigationBarTitleText": "设备台账详情",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/equipmentManagement/repair/index",
        "style": {
          "navigationBarTitleText": "设备报修",
          "navigationStyle": "custom"
        }
      "style": {
        "navigationBarTitleText": "设备报修",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/equipmentManagement/repair/add",
        "style": {
          "navigationBarTitleText": "新增设备报修",
          "navigationStyle": "custom"
        }
      "style": {
        "navigationBarTitleText": "新增设备报修",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/equipmentManagement/repair/maintain",
        "style": {
          "navigationBarTitleText": "设备维修",
          "navigationStyle": "custom"
        }
      "style": {
        "navigationBarTitleText": "设备维修",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/equipmentManagement/upkeep/index",
        "style": {
          "navigationBarTitleText": "设备保养",
          "navigationStyle": "custom"
        }
      "style": {
        "navigationBarTitleText": "设备保养",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/equipmentManagement/upkeep/add",
        "style": {
          "navigationBarTitleText": "新增保养计划",
          "navigationStyle": "custom"
        }
      "style": {
        "navigationBarTitleText": "新增保养计划",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/equipmentManagement/upkeep/maintain",
        "style": {
          "navigationBarTitleText": "维修保养",
          "navigationStyle": "custom"
        }
      "style": {
        "navigationBarTitleText": "维修保养",
        "navigationStyle": "custom"
      }
    }
  ],
  "subPackages": [
src/pages/cooperativeOffice/clientVisit/detail.vue
@@ -2,51 +2,54 @@
  <view class="client-visit-detail">
    <PageHeader title="客户拜访详情" @back="goBack" />
    
    <u-form @submit="handleSignIn" ref="formRef" label-width="110" input-align="right" error-message-align="right">
    <u-form @submit="handleSignIn" ref="formRef" label-width="90">
      <!-- å®¢æˆ·ä¿¡æ¯ -->
      <u-cell-group title="客户信息">
        <u-form-item label="客户名称" prop="customerName" required border-bottom>
          <u-input
            v-model="form.customerName"
            placeholder="请输入客户名称"
            readonly
          />
        </u-form-item>
        <u-form-item label="联系人" prop="contactPerson" border-bottom>
        <u-form-item label="联系人" prop="contact" border-bottom>
          <u-input
            v-model="form.contactPerson"
            v-model="form.contact"
            placeholder="请输入联系人"
            readonly
          />
        </u-form-item>
        <u-form-item label="联系电话" prop="contactPhone" border-bottom>
          <u-input
            v-model="form.contactPhone"
            placeholder="请输入联系电话"
            readonly
          />
        </u-form-item>
      </u-cell-group>
      <!-- æ‹œè®¿ä¿¡æ¯ -->
      <u-cell-group title="拜访信息">
        <u-form-item label="拜访目的" prop="visitPurpose" required border-bottom>
        <u-form-item label="拜访目的" prop="purposeVisit" required border-bottom>
          <u-input
            v-model="form.visitPurpose"
            v-model="form.purposeVisit"
            placeholder="请输入拜访目的"
          />
        </u-form-item>
        <u-form-item label="拜访时间" prop="visitTime" required border-bottom>
        <u-form-item label="拜访时间" prop="purposeDate" required border-bottom>
          <u-input
            v-model="form.visitTime"
            v-model="form.purposeDate"
            placeholder="请选择拜访时间"
            readonly
            @click="showTimePicker"
          />
          <template #right>
                    <up-icon
                        name="arrow-right"
                        @click="showTimePicker"
                    ></up-icon>
                </template>
        </u-form-item>
        <u-form-item label="拜访地点" prop="visitLocation" required border-bottom>
        <u-form-item label="拜访地点" prop="visitAddress" required border-bottom>
          <u-input
            v-model="form.visitLocation"
            v-model="form.visitAddress"
            placeholder="请输入拜访地点"
          >
            <template #suffix>
@@ -77,19 +80,19 @@
    </u-form>
    <!-- æ—¶é—´é€‰æ‹©å™¨ -->
    <u-popup v-model="showTime" mode="bottom">
      <u-datetime-picker
        v-model="currentTime"
        title="选择时间"
        @confirm="onTimeConfirm"
        @cancel="showTime = false"
      />
    </u-popup>
    <up-datetime-picker
                    :show="showTime"
                    v-model="currentTime"
                    @confirm="onTimeConfirm"
                    @cancel="showTime = false"
                    mode="datetime"
                />
  </view>
</template>
<script setup>
// æ›¿æ¢ toast æ–¹æ³•
defineOptions({name: 'client-visit-detail'})
const showToast = (message) => {
  uni.showToast({
    title: message,
@@ -103,6 +106,7 @@
import { clientVisitSignIn } from '@/api/cooperativeOffice/clientVisit'
import useUserStore from "@/store/modules/user"
import dayjs from "dayjs"
import { formatDateToYMD } from '@/utils/ruoyi'
const userStore = useUserStore()
@@ -126,7 +130,7 @@
const formRef = ref(null)
// æ—¶é—´ç›¸å…³
const currentTime = ref(new Date())
const currentTime = ref(Date.now())
const showTime = ref(false)
// è¿”回上一页
@@ -140,10 +144,11 @@
}
// ç¡®è®¤æ—¶é—´é€‰æ‹©
const onTimeConfirm = ({ selectedValues }) => {
  form.value.purposeDate = selectedValues.join('-')
  currentTime.value = selectedValues.join('-')
  showTime.value = false
const onTimeConfirm = (e) => {
  console.log(e)
  form.value.purposeDate = e.value
    currentTime.value = e.value
    showTime.value = false;
}
// èŽ·å–å½“å‰ä½ç½®
@@ -207,8 +212,6 @@
// æäº¤ç­¾åˆ°
const handleSignIn = async () => {
    console.log('form.value----', form.value);
  if (!form.value.customerName) {
    showToast('请输入客户名称')
    return
@@ -263,7 +266,7 @@
const initPageData = () => {
  // è®¾ç½®é»˜è®¤æ‹œè®¿æ—¶é—´ä¸ºå½“前时间
  form.value.purposeDate = dayjs().format('YYYY-MM-DD HH:mm:ss')
  currentTime.value = new Date()
  currentTime.value = Date.now()
  
  // è®¾ç½®æ‹œè®¿äººä¸ºå½“前登录用户的昵称
  form.value.visitingPeople = userStore.nickName || ''
@@ -275,6 +278,7 @@
</script>
<style scoped lang="scss">
@import '@/static/scss/form-common.scss';
.client-visit {
  min-height: 100vh;
  background: #f8f9fa;
src/pages/cooperativeOffice/clientVisit/index.vue
@@ -94,23 +94,23 @@
          
          <!-- æŒ‰é’®åŒºåŸŸ -->
          <view class="action-buttons">
            <van-button
            <u-button
              type="primary"
              size="small"
              class="action-btn"
              @click="viewDetail(item.id)"
            >
              æŸ¥çœ‹è¯¦æƒ…
            </van-button>
            <van-button
              type="danger"
            </u-button>
            <u-button
              type="error"
              size="small"
              plain
              class="action-btn"
              @click="deleteRecord(item.id)"
            >
              åˆ é™¤
            </van-button>
            </u-button>
          </view>
        </view>
      </view>
@@ -134,6 +134,13 @@
import { getVisitRecords, deleteVisitRecord } from '@/api/cooperativeOffice/clientVisit'
import useUserStore from "@/store/modules/user"
// æ›¿æ¢ toast æ–¹æ³•
defineOptions({name: 'client-visit-index'})
const showToast = (message) => {
  uni.showToast({
    title: message,
    icon: 'none'
  })
}
import dayjs from "dayjs"
src/pages/cooperativeOffice/collaborativeApproval/approve.vue
@@ -83,21 +83,20 @@
      </view>
      
      <view class="input-content">
        <van-field
        <u-textarea
          v-model="approvalOpinion"
          type="textarea"
          rows="4"
          placeholder="请输入审核意见"
          maxlength="200"
          show-word-limit
          count
        />
      </view>
    </view>
    <!-- åº•部操作按钮 -->
    <view v-if="canApprove" class="footer-actions">
      <van-button class="reject-btn" @click="handleReject">驳回</van-button>
      <van-button class="approve-btn" @click="handleApprove">通过</van-button>
      <u-button class="reject-btn" @click="handleReject">驳回</u-button>
      <u-button class="approve-btn" @click="handleApprove">通过</u-button>
    </view>
  </view>
</template>
@@ -471,18 +470,21 @@
}
.reject-btn {
  width: 120px;
  background: #ff4d4f;
  color: #fff;
  border: none;
}
    width: 120px;
    background: #ff4d4f;
    color: #fff;
  }
.approve-btn {
  width: 120px;
  background: #52c41a;
  color: #fff;
  border: none;
}
  .approve-btn {
    width: 120px;
    background: #52c41a;
    color: #fff;
  }
  /* é€‚配u-button样式 */
  :deep(.u-button) {
    border-radius: 6px;
  }
@keyframes pulse {
  0% {
src/pages/cooperativeOffice/collaborativeApproval/contactSelect.vue
@@ -30,7 +30,7 @@
        <view class="contact-details">
          <text class="contact-name">{{ selectedContact.nickName }}</text>
        </view>
        <van-icon name="cross" size="16" color="#999" @click="clearSelected" />
        <u-icon name="close" size="16" color="#999" @click="clearSelected" />
      </view>
    </view>
@@ -40,7 +40,7 @@
        <text class="list-title">全部联系人</text>
      </view>
      
      <van-list
      <u-list
        v-model:loading="loading"
        :finished="finished"
        finished-text="没有更多了"
@@ -63,7 +63,7 @@
            </view>
          </view>
        </view>
      </van-list>
      </u-list>
    </view>
  </view>
</template>
@@ -330,53 +330,7 @@
}
// è‡ªå®šä¹‰å•选按钮样式
:deep(.van-radio) {
  .van-radio__icon {
    width: 20px;
    height: 20px;
    border: 2px solid #ddd;
    border-radius: 50%;
    background: #fff;
    position: relative;
    transition: all 0.2s;
    &::before {
      content: '';
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%) scale(0);
      width: 8px;
      height: 8px;
      background: #006cfb;
      border-radius: 50%;
      transition: transform 0.2s;
    }
  }
  &.van-radio--checked {
    .van-radio__icon {
      border-color: #006cfb;
      background: #fff;
      &::before {
        transform: translate(-50%, -50%) scale(1);
      }
      &::after {
        content: '';
        position: absolute;
        top: -2px;
        left: -2px;
        right: -2px;
        bottom: -2px;
        border: 2px solid rgba(0, 108, 251, 0.2);
        border-radius: 50%;
        animation: ripple 0.6s ease-out;
      }
    }
  }
}
/* uview-plus的radio组件样式不需要额外的deep样式穿透,保持原有样式 */
@keyframes ripple {
  0% {
src/pages/cooperativeOffice/collaborativeApproval/detail.vue
@@ -3,75 +3,68 @@
    <PageHeader title="审批流程" @back="goBack" />
    <!-- è¡¨å•区域 -->
    <view class="form-section">
      <van-form ref="formRef" @submit="submitForm" :rules="rules" input-align="right" error-message-align="right" scroll-to-error scroll-to-error-position="center">
                <van-cell-group style="margin-bottom: 16px;">
                    <van-field
                        v-model="form.approveReason"
                        name="approveReason"
                        rows="2"
                        autosize
                        label="申请事由"
                        type="textarea"
                        maxlength="200"
                        :rules="[{ required: true, message: '申请事由不能为空' }]"
                        placeholder="请输入申请事由"
                        show-word-limit
                        required
                    />
                </van-cell-group>
                <van-cell-group>
                    <van-field
                        v-model="form.approveDeptName"
                        readonly
                        name="picker"
                        label="申请部门"
                        placeholder="请选择申请部门"
                        :rules="[{ required: true, message: '请选择申请部门' }]"
    <u-form ref="formRef" @submit="submitForm" :rules="rules" :model="form" label-width="140rpx">
      <u-form-item prop="approveReason" label="申请事由" required>
        <u-input
          v-model="form.approveReason"
          type="textarea"
          rows="2"
          auto-height
          maxlength="200"
          placeholder="请输入申请事由"
          show-word-limit
        />
      </u-form-item>
      <u-form-item prop="approveDeptName" label="申请部门" required>
        <u-input
          v-model="form.approveDeptName"
          readonly
          placeholder="请选择申请部门"
          @click="showPicker = true"
        />
        <template #right>
                    <up-icon
                        name="arrow-right"
                        @click="showPicker = true"
                        required
                    />
          <van-field
            v-model="form.approveUserName"
            name="taxPrice"
            label="申请人"
            placeholder="请输入申请人"
            :rules="[{ required: true, message: '申请人不能为空' }]"
            required
            readonly
          />
          <van-popup
            v-model:show="showPicker"
            position="bottom"
          >
            <van-picker
              :columns="productOptions"
              :model-value="pickerValue"
              @confirm="onConfirm"
              @cancel="showPicker = false"
            />
          </van-popup>
                    <van-field
                        v-model="form.approveTime"
                        label="申请日期"
                        placeholder="请选择"
                        readonly
                        required
                        @click="showDatePicker"
                        :rules="[{ required: true, message: '请选择来款日期' }]"
                    />
                    <!-- æ—¥æœŸé€‰æ‹©å™¨ -->
                    <van-popup v-model:show="showDate" position="bottom">
                        <van-date-picker
                            v-model="currentDate"
                            title="选择日期"
                            @confirm="onDateConfirm"
                            @cancel="showDate = false"
                        />
                    </van-popup>
        </van-cell-group>
      </van-form>
    </view>
                    ></up-icon>
                </template>
      </u-form-item>
      <u-form-item prop="approveUser" label="申请人" required>
        <u-input
          v-model="form.approveUserName"
          placeholder="请输入申请人"
          readonly
        />
      </u-form-item>
      <u-form-item prop="approveTime" label="申请日期" required>
        <u-input
          v-model="form.approveTime"
          placeholder="请选择"
          readonly
          @click="showDatePicker"
        />
      </u-form-item>
    </u-form>
    <!-- é€‰æ‹©å™¨å¼¹çª— -->
    <up-action-sheet
      :show="showPicker"
      :actions="productOptions"
      title="选择部门"
      @select="onConfirm"
      @close="showPicker = false"
    />
    <!-- æ—¥æœŸé€‰æ‹©å™¨ -->
    <u-popup v-model="showDate" mode="bottom">
      <u-datetime-picker
        v-model="currentDate"
        title="选择日期"
        mode="date"
        @confirm="onDateConfirm"
        @cancel="showDate = false"
      />
    </u-popup>
    <!-- å®¡æ ¸æµç¨‹åŒºåŸŸ -->
    <view class="approval-process">
      <view class="approval-header">
@@ -107,14 +100,14 @@
      </view>
      <view class="add-step-btn">
                <van-button icon="plus" plain type="primary" style="width: 100%" @click="addApprovalStep">新增节点</van-button>
            <u-button icon="plus" plain type="primary" style="width: 100%" @click="addApprovalStep">新增节点</u-button>
      </view>
    </view>
    <!-- åº•部按钮 -->
    <view class="footer-btns">
      <van-button class="cancel-btn" @click="goBack">取消</van-button>
      <van-button class="save-btn" @click="submitForm">保存</van-button>
      <u-button class="cancel-btn" @click="goBack">取消</u-button>
      <u-button class="save-btn" @click="submitForm">保存</u-button>
    </view>
  </view>
</template>
@@ -123,6 +116,7 @@
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
import PageHeader from "@/components/PageHeader.vue";
import useUserStore from "@/store/modules/user";
import { formatDateToYMD } from '@/utils/ruoyi'
import {getDept, approveProcessGetInfo, approveProcessAdd, approveProcessUpdate} from "@/api/collaborativeApproval/approvalProcess";
const showToast = (message) => {
    uni.showToast({
@@ -148,7 +142,6 @@
    rules: {
        approveTime: [{ required: false, message: "请输入", trigger: "change" },],
        approveId: [{ required: false, message: "请输入", trigger: "blur" }],
        approveUser: [{ required: false, message: "请输入", trigger: "blur" }],
        approveDeptId: [{ required: true, message: "请输入", trigger: "blur" }],
        approveReason: [{ required: true, message: "请输入", trigger: "blur" }],
        checkResult: [{ required: false, message: "请输入", trigger: "blur" }],
@@ -156,7 +149,6 @@
});
const { form, rules } = toRefs(data);
const result = ref("");
const pickerValue = ref([]);
const showPicker = ref(false);
const productOptions = ref([]);
const operationType = ref("");
@@ -173,7 +165,7 @@
    getDept().then((res) => {
        productOptions.value = res.data.map(item => ({
            value: item.deptId,
            text: item.deptName
            name: item.deptName
        }))
    });
};
@@ -245,10 +237,15 @@
  uni.$off('selectContact', handleSelectContact);
});
const onConfirm = ({ selectedValues, selectedOptions }) => {
  form.value.approveDeptName = selectedOptions[0]?.text;
  form.value.approveDeptId = selectedOptions[0]?.value;
  pickerValue.value = selectedValues;
const onConfirm = (item) => {
  // è®¾ç½®é€‰ä¸­çš„部门
  form.value.approveDeptName = item.name;
  // ç¡®ä¿è®¾ç½®çš„æ˜¯å­—符串类型的部门ID
  form.value.approveDeptId = String(item.value || '');
  console.log('部门选择后的值:', {
    approveDeptId: form.value.approveDeptId,
    approveDeptName: form.value.approveDeptName
  });
  showPicker.value = false;
};
@@ -266,38 +263,59 @@
    return;
  }
  
  formRef.value.validate().then(() => {
    // è¡¨å•校验通过,可以提交数据
        // æ”¶é›†æ‰€æœ‰èŠ‚ç‚¹çš„å®¡æ‰¹äººid
        console.log('approverNodes---', approverNodes.value)
        form.value.approveUserIds = approverNodes.value.map(node => node.userId).join(',')
        form.value.approveType = 0
        if (operationType.value === "add" || currentApproveStatus.value == 3) {
            approveProcessAdd(form.value).then(res => {
                showToast("提交成功");
                goBack()
            })
        } else {
            approveProcessUpdate(form.value).then(res => {
                showToast("提交成功");
                goBack()
            })
        }
  // æ‰‹åŠ¨æ£€æŸ¥å¿…å¡«å­—æ®µï¼Œé˜²æ­¢å› æ•°æ®ç±»åž‹é—®é¢˜å¯¼è‡´çš„æ ¡éªŒå¤±è´¥
  if (!form.value.approveReason || !form.value.approveReason.trim()) {
    showToast('请输入申请事由');
    return;
  }
  if (!form.value.approveDeptId || String(form.value.approveDeptId).trim() === '') {
    showToast('请选择申请部门');
    return;
  }
  if (!form.value.approveTime) {
    showToast('请选择申请日期');
    return;
  }
  formRef.value.validate().then((valid) => {
    if (valid) {
      // è¡¨å•校验通过,可以提交数据
      // æ”¶é›†æ‰€æœ‰èŠ‚ç‚¹çš„å®¡æ‰¹äººid
      console.log('approverNodes---', approverNodes.value)
      form.value.approveUserIds = approverNodes.value.map(node => node.userId).join(',')
      form.value.approveType = 0
      if (operationType.value === "add" || currentApproveStatus.value == 3) {
        approveProcessAdd(form.value).then(res => {
          showToast("提交成功");
          goBack()
        })
      } else {
        approveProcessUpdate(form.value).then(res => {
          showToast("提交成功");
          goBack()
        })
      }
    }
  }).catch((error) => {
    console.error("表单校验失败:", error);
    // æ˜¾ç¤ºå…·ä½“的错误信息
    if (error.length > 0) {
      const firstError = error[0];
      uni.showToast({
        title: firstError.message || '表单校验失败',
        icon: 'none'
      });
    } else {
      uni.showToast({
        title: '表单校验失败,请检查必填项',
        icon: 'none'
      });
    // å°è¯•获取具体的错误字段
    if (error && error.errors) {
      const firstError = error.errors[0];
      if (firstError) {
        uni.showToast({
          title: firstError.message || '表单校验失败,请检查必填项',
          icon: 'none'
        });
        return;
      }
    }
    // æ˜¾ç¤ºé€šç”¨é”™è¯¯ä¿¡æ¯
    uni.showToast({
      title: '表单校验失败,请检查必填项',
      icon: 'none'
    });
  });
};
@@ -344,10 +362,10 @@
}
// ç¡®è®¤æ—¥æœŸé€‰æ‹©
const onDateConfirm = ({ selectedValues }) => {
    form.value.approveTime = selectedValues.join('-')
    currentDate.value = selectedValues
    showDate.value = false
const onDateConfirm = (e) => {
  form.value.approveTime = formatDateToYMD(e.value)
    currentDate.value = formatDateToYMD(e.value)
    showDate.value = false;
}
// èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
function getCurrentDate() {
@@ -360,34 +378,7 @@
</script>
<style scoped lang="scss">
.account-detail {
  min-height: 100vh;
  background: #f8f9fa;
  padding-bottom: 80px;
}
.header {
  display: flex;
  align-items: center;
  background: #fff;
  padding: 16px 20px;
  border-bottom: 1px solid #f0f0f0;
  position: sticky;
  top: 0;
  z-index: 100;
}
.title {
  flex: 1;
  text-align: center;
  font-size: 18px;
  font-weight: 600;
  color: #333;
}
.form-section {
  margin-top: 16px;
}
@import '@/static/scss/form-common.scss';
.approval-process {
  background: #fff;
src/pages/cooperativeOffice/collaborativeApproval/index.vue
@@ -32,7 +32,7 @@
                            <text class="item-id">{{ item.approveId }}</text>
                        </view>
                        <view class="item-tag">
                            <van-tag :type="getTagClass(item.approveStatus)" size="medium">{{ formatReceiptType(item.approveStatus) }}</van-tag>
                            <u-tag :type="getTagClass(item.approveStatus)">{{ formatReceiptType(item.approveStatus) }}</u-tag>
                        </view>
                    </view>
                    <up-divider></up-divider>
@@ -70,7 +70,7 @@
                            </view>
                            <view class="detail-row">
                                <view class="actions">
                                    <van-button
                                    <u-button
                                        type="primary"
                                        size="small"
                                        class="action-btn edit"
@@ -78,8 +78,8 @@
                                        @click="handleItemClick(item)"
                                    >
                                        ç¼–辑
                                    </van-button>
                                    <van-button
                                    </u-button>
                                    <u-button
                                        type="success"
                                        size="small"
                                        class="action-btn approve"
@@ -87,7 +87,7 @@
                                        @click="approve(item)"
                                    >
                                        å®¡æ ¸
                                    </van-button>
                                    </u-button>
                                </view>
                            </view>
                        </view>
@@ -181,7 +181,7 @@
        } else if (type == 4) {
            return "primary";
        } else {
            return "danger";
            return "error";
        }
    };
@@ -461,7 +461,5 @@
    .action-btn.approve {
        /* success æ ·å¼æ¥è‡ªç»„件,这里保留钩子以便后续需要扩展 */
    }
    :deep(.van-floating-bubble) {
        background: #ed8d05;
    }
    /* å·²ç§»é™¤vant组件的样式引用 */
</style>
src/pages/equipmentManagement/ledger/detail.vue
@@ -4,7 +4,7 @@
        <PageHeader title="设备台账详情" @back="goBack" />
        
        <!-- è¡¨å•内容 -->
        <u-form @submit="sendForm" ref="formRef" label-width="110" input-align="right" error-message-align="right">
        <u-form @submit="sendForm" ref="formRef" :rules="formRules" label-width="110">
            <!-- åŸºæœ¬ä¿¡æ¯ -->
            <u-cell-group title="基本信息">
                <u-form-item label="设备名称" prop="deviceName" required border-bottom>
@@ -17,102 +17,128 @@
                <u-form-item label="规格型号" prop="deviceModel" required border-bottom>
                    <u-input
                        v-model="form.deviceModel"
                        :disabled="(form.deviceModel != null && operationType === 'edit')"
                        placeholder="请输入规格型号"
                        clearable
                    />
                </u-form-item>
                <u-form-item label="设备编号" prop="deviceNumber" required border-bottom>
                <u-form-item label="设备品牌" prop="deviceBrand" required border-bottom>
                    <u-input
                        v-model="form.deviceNumber"
                        placeholder="请输入设备编号"
                        v-model="form.deviceBrand"
                        placeholder="请输入设备品牌"
                        clearable
                    />
                </u-form-item>
                <u-form-item label="购买日期" prop="purchaseDate" required border-bottom>
                <u-form-item label="供应商" prop="supplierName" required border-bottom>
                    <u-input
                        v-model="form.purchaseDate"
                        placeholder="请选择购买日期"
                        readonly
                        @click="showDatePicker"
                        clearable
                    />
                </u-form-item>
                <u-form-item label="è´­ä¹°ä»·æ ¼" prop="purchasePrice" required border-bottom>
                    <u-input
                        v-model="form.purchasePrice"
                        type="number"
                        placeholder="请输入购买价格"
                        clearable
                    />
                </u-form-item>
                <u-form-item label="供应商" prop="supplier" required border-bottom>
                    <u-input
                        v-model="form.supplier"
                        v-model="form.supplierName"
                        placeholder="请输入供应商"
                        clearable
                    />
                </u-form-item>
                <u-form-item label="使用部门" prop="department" required border-bottom>
                <u-form-item label="存放位置" prop="storageLocation" required border-bottom>
                    <u-input
                        v-model="form.department"
                        placeholder="请输入使用部门"
                        v-model="form.storageLocation"
                        placeholder="请输入存放位置"
                        clearable
                    />
                </u-form-item>
                <u-form-item label="税率" prop="taxRate" required border-bottom>
                <u-form-item label="单位" prop="unit" required border-bottom>
                    <u-input
                        v-model="form.unit"
                        placeholder="请输入单位"
                        clearable
                    />
                </u-form-item>
                <u-form-item label="启用折旧" prop="enableDepreciation" required border-bottom>
                    <u-switch
                        v-model="form.enableDepreciation"
                        :active-value="true"
                        :inactive-value="false"
                    />
                </u-form-item>
                <u-form-item label="数量" prop="number" required border-bottom>
                    <u-input
                        v-model="form.number"
                        type="number"
                        placeholder="请输入数量"
                        clearable
                        @blur="mathNum"
                    />
                </u-form-item>
                <u-form-item label="含税单价" prop="taxIncludingPriceUnit" required border-bottom>
                    <u-input
                        v-model="form.taxIncludingPriceUnit"
                        type="number"
                        placeholder="请输入含税单价"
                        clearable
                        @blur="mathNum"
                    />
                </u-form-item>
                <u-form-item label="含税总价" prop="taxIncludingPriceTotal" required border-bottom>
                    <u-input
                        v-model="form.taxIncludingPriceTotal"
                        type="number"
                        placeholder="自动生成"
                        disabled
                    />
                </u-form-item>
                <u-form-item label="税率(%)" prop="taxRate" required border-bottom>
                    <u-input
                        v-model="form.taxRate"
                        placeholder="请选择税率"
                        readonly
                        @click="showTaxRatePicker"
                        clearable
                        @click="showTaxRatePicker = true"
                    />
                    <template #right>
                        <u-icon name="arrow-right" @click="showTaxRatePicker = true"></u-icon>
                    </template>
                    <up-action-sheet
                        :show="showTaxRatePicker"
                        :actions="taxRateActionList"
                        title="选择税率"
                        @select="onTaxRateSelect"
                        @close="showTaxRatePicker = false"
                    />
                </u-form-item>
                <u-form-item label="使用状态" prop="status" required border-bottom>
                <u-form-item label="不含税总价" prop="unTaxIncludingPriceTotal" required border-bottom>
                    <u-input
                        v-model="form.status"
                        placeholder="请输入使用状态"
                        clearable
                        v-model="form.unTaxIncludingPriceTotal"
                        type="number"
                        placeholder="自动生成"
                        disabled
                    />
                </u-form-item>
                <u-form-item label="备注" border-bottom>
                    <u-textarea
                        v-model="form.remark"
                        placeholder="请输入备注"
                        :maxlength="200"
                        count
                        :autoHeight="true"
                <u-form-item label="录入日期" prop="createTime" required border-bottom>
                    <u-input
                        v-model="form.createTime"
                        placeholder="请选择录入日期"
                        readonly
                        @click="showDatePicker"
                        clearable
                    />
                    <template #right>
                        <u-icon name="arrow-right" @click="showDatePicker"></u-icon>
                    </template>
                </u-form-item>
            </u-cell-group>
            <!-- æäº¤æŒ‰é’® -->
            <view class="footer-btns">
                <u-button class="cancel-btn" @click="goBack">取消</u-button>
                <u-button class="save-btn" type="primary" @click="sendForm" :loading="loading">保存</u-button>
            </view>
        </u-form>
        <!-- æäº¤æŒ‰é’® -->
        <view class="footer-btns">
            <u-button class="cancel-btn" @click="goBack">取消</u-button>
            <u-button class="save-btn" type="primary" @click="sendForm" :loading="loading">保存</u-button>
        </view>
    </u-form>
        <!-- ç¨ŽçŽ‡é€‰æ‹©å™¨ -->
        <u-popup v-model="showTaxRate" mode="bottom">
            <u-picker
                v-model="taxRatePickerValue"
                :columns="taxRateOptions"
                @confirm="onTaxRateConfirm"
                @cancel="showTaxRate = false"
            />
        </u-popup>
        <!-- æ—¥æœŸé€‰æ‹©å™¨ -->
        <u-popup v-model="showDate" mode="bottom">
            <u-datetime-picker
                v-model="currentDate"
                title="选择日期"
    <!-- æ—¥æœŸé€‰æ‹©å™¨ -->
<up-datetime-picker
                :show="showDate"
                v-model="pickerDateValue"
                @confirm="onDateConfirm"
                @cancel="showDate = false"
                mode="date"
            />
        </u-popup>
    </view>
</view>
</template>
<script setup>
@@ -143,28 +169,38 @@
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 pickerDateValue = ref(Date.now());
const showTaxRatePicker = ref(false);
const taxRateActionList = ref([
  { name: '1', value: 1 },
  { name: '6', value: 6 },
  { name: '13', value: 13 }
]);
// è¡¨å•验证规则
const formRules = {
    deviceName: [{ required: true, trigger: "blur", message: "请输入" }],
    deviceModel: [{ required: true, trigger: "blur", message: "请输入" }],
    deviceBrand: [{ required: true, trigger: "blur", message: "请输入" }],
    supplierName: [{ required: true, trigger: "blur", message: "请输入" }],
    storageLocation: [{ 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: "请输入" }],
    createTime: [{ required: true, trigger: "change", message: "请选择" }],
};
// ä½¿ç”¨ ref å£°æ˜Žè¡¨å•数据
const form = ref({
    deviceName: undefined, // è®¾å¤‡åç§°
    deviceModel: undefined, // è§„格型号
    deviceBrand: undefined, // è®¾å¤‡å“ç‰Œ
    supplierName: undefined, // ä¾›åº”商
    storageLocation: undefined, // å­˜æ”¾ä½ç½®
    unit: undefined, // å•位
    enableDepreciation: false, // å¯ç”¨æŠ˜æ—§
    number: undefined, // æ•°é‡
    taxIncludingPriceUnit: undefined, // å«ç¨Žå•ä»·
    taxIncludingPriceTotal: undefined, // å«ç¨Žæ€»ä»·
@@ -173,14 +209,7 @@
    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) => {
@@ -192,8 +221,11 @@
        if (code == 200) {
            form.value.deviceName = data.deviceName;
            form.value.deviceModel = data.deviceModel;
            form.value.deviceBrand = data.deviceBrand || '';
            form.value.supplierName = data.supplierName;
            form.value.storageLocation = data.storageLocation || '';
            form.value.unit = data.unit;
            form.value.enableDepreciation = !!data.enableDepreciation;
            form.value.number = data.number;
            form.value.taxIncludingPriceUnit = data.taxIncludingPriceUnit;
            form.value.taxIncludingPriceTotal = data.taxIncludingPriceTotal;
@@ -238,14 +270,17 @@
    form.value = {
        deviceName: undefined,
        deviceModel: undefined,
        deviceBrand: undefined,
        supplierName: undefined,
        storageLocation: undefined,
        unit: undefined,
        enableDepreciation: false,
        number: undefined,
        taxIncludingPriceUnit: undefined,
        taxIncludingPriceTotal: undefined,
        taxRate: undefined,
        unTaxIncludingPriceTotal: undefined,
        createTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
        createTime: dayjs().format("YYYY-MM-DD"),
    };
};
@@ -316,16 +351,18 @@
    return options.id;
};
// æ˜¾ç¤ºç¨ŽçŽ‡é€‰æ‹©å™¨
const showTaxRatePicker = () => {
    showTaxRate.value = true;
};
// ç¡®è®¤ç¨ŽçŽ‡é€‰æ‹©
const onTaxRateConfirm = ({ selectedValues, selectedOptions }) => {
    form.value.taxRate = selectedOptions[0].value;
    taxRatePickerValue.value = selectedValues;
    showTaxRate.value = false;
const onTaxRateConfirm = (e) => {
    form.value.taxRate = e.value;
    mathNum(); // é‡æ–°è®¡ç®—
};
// é€‰æ‹©ç¨Žçއ
const onTaxRateSelect = (e) => {
    form.value.taxRate = e.value;
    showTaxRatePicker.value = false;
    mathNum(); // é‡æ–°è®¡ç®—
};
@@ -335,10 +372,12 @@
};
// ç¡®è®¤æ—¥æœŸé€‰æ‹©
const onDateConfirm = ({ selectedValues }) => {
const onDateConfirm = (e) => {
    // åªä¿å­˜å¹´æœˆæ—¥ï¼Œä¸åŒ…含时分秒
    form.value.createTime = selectedValues.join('-');
    currentDate.value = selectedValues;
    const date = new Date(e.value);
    form.value.createTime = date.getFullYear() + '-' +
        String(date.getMonth() + 1).padStart(2, '0') + '-' +
        String(date.getDate()).padStart(2, '0');
    showDate.value = false;
};
@@ -354,6 +393,7 @@
</script>
<style scoped lang="scss">
@import '@/static/scss/form-common.scss';
.ledger-detail {
    min-height: 100vh;
    background: #f8f9fa;
src/pages/equipmentManagement/repair/add.vue
@@ -4,88 +4,82 @@
        <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">
        <u-form @submit="sendForm" ref="formRef" :rules="formRules" label-width="110" input-align="right" error-message-align="right">
            <!-- åŸºæœ¬ä¿¡æ¯ -->
            <van-cell-group title="基本信息" inset>
                <van-field
                    v-model="deviceNameText"
                    label="设备名称"
                    placeholder="请选择设备名称"
                    :rules="formRules.deviceLedgerId"
                    required
                    readonly
                    @click="showDevicePicker"
                    clearable
                >
                    <template #right-icon>
                        <van-icon name="scan" @click.stop="startScan" class="scan-icon" />
            <u-cell-group title="基本信息">
                <u-form-item label="设备名称" prop="deviceLedgerId" required border-bottom>
                    <u-input
                        v-model="deviceNameText"
                        placeholder="请选择设备名称"
                        @click="showDevicePicker"
                        clearable
                    />
                    <template #right>
                        <u-icon name="scan" @click="startScan" class="scan-icon" />
                    </template>
                </van-field>
                <van-field
                    v-model="form.deviceModel"
                    label="规格型号"
                    placeholder="请输入规格型号"
                    readonly
                    clearable
                />
                <van-field
                    v-model="form.repairTime"
                    label="报修日期"
                    placeholder="请选择报修日期"
                    :rules="formRules.repairTime"
                    required
                    readonly
                    @click="showDatePicker"
                    clearable
                />
                <van-field
                    v-model="form.repairName"
                    label="报修人"
                    placeholder="请输入报修人"
                    :rules="formRules.repairName"
                    required
                    clearable
                />
                <van-field
                    v-model="form.remark"
                    label="故障现象"
                    type="textarea"
                    rows="3"
                    placeholder="请输入故障现象"
                    :rules="formRules.remark"
                    required
                    clearable
                    maxlength="200"
                    show-word-limit
                />
            </van-cell-group>
                </u-form-item>
                <u-form-item label="规格型号" prop="deviceModel" border-bottom>
                    <u-input
                        v-model="form.deviceModel"
                        placeholder="请输入规格型号"
                        clearable
                    />
                </u-form-item>
                <u-form-item label="报修日期" prop="repairTime" required border-bottom>
                    <u-input
                        v-model="form.repairTime"
                        placeholder="请选择报修日期"
                        readonly
                        @click="showDatePicker"
                        clearable
                    />
                    <template #right>
                        <u-icon name="arrow-right" @click="showDatePicker"></u-icon>
                    </template>
                </u-form-item>
                <u-form-item label="报修人" prop="repairName" required border-bottom>
                    <u-input
                        v-model="form.repairName"
                        placeholder="请输入报修人"
                        clearable
                    />
                </u-form-item>
                <u-form-item label="故障现象" prop="remark" required border-bottom>
                    <u-textarea
                        v-model="form.remark"
                        rows="3"
                        placeholder="请输入故障现象"
                        clearable
                        count
                        maxlength="200"
                    />
                </u-form-item>
            </u-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>
                <u-button class="cancel-btn" @click="goBack">取消</u-button>
                <u-button class="save-btn" type="primary" @click="sendForm" :loading="loading">保存</u-button>
            </view>
        </van-form>
        </u-form>
        <!-- è®¾å¤‡é€‰æ‹©å™¨ -->
        <van-popup v-model:show="showDevice" position="bottom">
            <van-picker
                :model-value="devicePickerValue"
                :columns="deviceColumns"
                @confirm="onDeviceConfirm"
                @cancel="showDevice = false"
            />
        </van-popup>
        <up-action-sheet
            :show="showDevice"
            :actions="deviceActionList"
            title="选择设备名称"
            @select="onDeviceSelect"
            @close="showDevice = false"
        />
        <!-- æ—¥æœŸé€‰æ‹©å™¨ -->
        <van-popup v-model:show="showDate" position="bottom">
            <van-date-picker
                v-model="currentDate"
                title="选择日期"
                @confirm="onDateConfirm"
                @cancel="showDate = false"
            />
        </van-popup>
        <up-datetime-picker
            :show="showDate"
            v-model="pickerDateValue"
            @confirm="onDateConfirm"
            @cancel="showDate = false"
            mode="date"
        />
    </view>
</template>
@@ -96,7 +90,13 @@
import { getDeviceLedger } from '@/api/equipmentManagement/ledger';
import { addRepair, editRepair, getRepairById } from '@/api/equipmentManagement/repair';
import dayjs from "dayjs";
import { showToast } from 'vant';
import { formatDateToYMD } from '@/utils/ruoyi';
const showToast = (message) => {
    uni.showToast({
        title: message,
        icon: 'none'
    })
}
defineOptions({
    name: "设备报修表单",
@@ -107,13 +107,18 @@
const operationType = ref('add');
const loading = ref(false);
const showDevice = ref(false);
const devicePickerValue = ref([]);
const showDate = ref(false);
const currentDate = ref([new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()]);
const pickerDateValue = ref(Date.now());
// è®¾å¤‡é€‰é¡¹
const deviceOptions = ref([]);
const deviceNameText = ref('');
const deviceActionList = computed(() => {
    return deviceOptions.value.map(item => ({
        name: item.deviceName,
        value: item.id
    }));
});
// æ‰«ç ç›¸å…³çŠ¶æ€
const isScanning = ref(false);
@@ -134,14 +139,6 @@
    repairTime: dayjs().format("YYYY-MM-DD"), // æŠ¥ä¿®æ—¥æœŸ
    repairName: undefined, // æŠ¥ä¿®äºº
    remark: undefined, // æ•…障现象
});
// è®¾å¤‡é€‰æ‹©å™¨åˆ—
const deviceColumns = computed(() => {
    return deviceOptions.value.map(item => ({
        text: item.deviceName,
        value: item.id
    }));
});
// åŠ è½½è®¾å¤‡åˆ—è¡¨
@@ -277,11 +274,10 @@
};
// ç¡®è®¤è®¾å¤‡é€‰æ‹©
const onDeviceConfirm = ({ selectedValues, selectedOptions }) => {
    form.value.deviceLedgerId = selectedOptions[0].value;
    devicePickerValue.value = selectedValues;
const onDeviceSelect = (e) => {
    form.value.deviceLedgerId = e.value;
    setDeviceModel(e.value);
    showDevice.value = false;
    setDeviceModel(selectedOptions[0].value);
};
// æ˜¾ç¤ºæ—¥æœŸé€‰æ‹©å™¨
@@ -290,9 +286,8 @@
};
// ç¡®è®¤æ—¥æœŸé€‰æ‹©
const onDateConfirm = ({ selectedValues }) => {
    form.value.repairTime = selectedValues.join('-');
    currentDate.value = selectedValues;
const onDateConfirm = (e) => {
    form.value.repairTime = formatDateToYMD(e.value);
    showDate.value = false;
};
@@ -375,6 +370,7 @@
</script>
<style scoped lang="scss">
@import '@/static/scss/form-common.scss';
.repair-add {
    min-height: 100vh;
    background: #f8f9fa;
src/pages/equipmentManagement/repair/index.vue
@@ -33,8 +33,8 @@
              <text class="item-id">设备名称:{{ item.deviceName }}</text>
            </view>
            <view class="status-tag">
              <van-tag v-if="item.status === 1" type="success">完结</van-tag>
              <van-tag v-if="item.status === 0" type="danger">待维修</van-tag>
              <u-tag v-if="item.status === 1" type="success">完结</u-tag>
              <u-tag v-if="item.status === 0" type="error">待维修</u-tag>
            </view>
          </view>
          <up-divider></up-divider>
@@ -72,15 +72,15 @@
          
          <!-- æŒ‰é’®åŒºåŸŸ -->
          <view class="action-buttons">
            <van-button
            <u-button
              type="primary"
              size="small"
              class="action-btn"
              @click="edit(item.id)"
            >
              ç¼–辑
            </van-button>
            <van-button
            </u-button>
            <u-button
              type="warning"
              size="small"
              class="action-btn"
@@ -88,16 +88,16 @@
              @click="addMaintain(item.id)"
            >
              æ–°å¢žç»´ä¿®
            </van-button>
            <van-button
              type="danger"
            </u-button>
            <u-button
              type="error"
              size="small"
              plain
              class="action-btn"
              @click="delRepairByIds(item.id)"
            >
              åˆ é™¤
            </van-button>
            </u-button>
          </view>
        </view>
      </view>
@@ -106,13 +106,10 @@
    <view v-else class="no-data">
      <text>暂无设备报修数据</text>
    </view>
    <!-- æµ®åŠ¨æ°”æ³¡æŒ‰é’® -->
    <van-floating-bubble
      axis="xy"
      icon="plus"
      @click="addRepair"
    />
    <!-- æµ®åŠ¨æ“ä½œæŒ‰é’® -->
        <view class="fab-button" @click="addRepair">
            <up-icon name="plus" size="24" color="#ffffff"></up-icon>
        </view>
  </view>
</template>
@@ -122,7 +119,13 @@
import PageHeader from '@/components/PageHeader.vue'
import { getRepairPage, delRepair } from '@/api/equipmentManagement/repair'
import useUserStore from "@/store/modules/user"
import { showToast } from 'vant';
const showToast = (message) => {
  uni.showToast({
    title: message,
    icon: 'none'
  })
}
const userStore = useUserStore()
@@ -374,4 +377,19 @@
.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/equipmentManagement/repair/maintain.vue
@@ -4,57 +4,56 @@
        <PageHeader title="新增维修" @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">
        <u-form ref="formRef" :model="form" :rules="formRules" label-width="140rpx">
            <!-- åŸºæœ¬ä¿¡æ¯ -->
            <van-cell-group title="维修信息" inset>
                <van-field
                    v-model="form.maintenanceName"
                    label="维修人"
                    placeholder="请输入维修人"
                    :rules="formRules.maintenanceName"
                    required
                    clearable
                />
                <van-field
                    v-model="form.maintenanceResult"
                    label="维修结果"
                    type="textarea"
                    rows="3"
                    placeholder="请输入维修结果"
                    :rules="formRules.maintenanceResult"
                    required
                    clearable
                    maxlength="200"
                    show-word-limit
                />
                <van-field
                    v-model="form.maintenanceTime"
                    label="维修日期"
                    placeholder="请选择维修日期"
                    :rules="formRules.maintenanceTime"
                    required
                    readonly
                    @click="showDatePicker"
                    clearable
                />
            </van-cell-group>
            <u-cell-group title="维修信息" inset>
                <u-form-item prop="maintenanceName" label="维修人" required>
                    <u-input
                        v-model="form.maintenanceName"
                        placeholder="请输入维修人"
                        clearable
                    />
                </u-form-item>
                <u-form-item prop="maintenanceResult" label="维修结果" required>
                    <u-input
                        v-model="form.maintenanceResult"
                        type="textarea"
                        rows="3"
                        placeholder="请输入维修结果"
                        clearable
                        maxlength="200"
                        show-word-limit
                    />
                </u-form-item>
                <u-form-item label="维修日期" prop="maintenanceTime" required border-bottom>
                    <u-input
                        v-model="form.maintenanceTime"
                        placeholder="请选择维修日期"
                        readonly
                        @click="showDatePicker = true"
                        clearable
                    />
                    <template #right>
                        <u-icon name="arrow-right" @click="showDatePicker = true"></u-icon>
                    </template>
                </u-form-item>
            </u-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>
                <u-button class="cancel-btn" @click="goBack">取消</u-button>
                <u-button class="save-btn" @click="submitForm" :loading="loading">保存</u-button>
            </view>
        </van-form>
        </u-form>
        <!-- æ—¥æœŸé€‰æ‹©å™¨ -->
        <van-popup v-model:show="showDate" position="bottom">
            <van-date-picker
                v-model="currentDate"
                title="选择日期"
                @confirm="onDateConfirm"
                @cancel="showDate = false"
            />
        </van-popup>
        <up-datetime-picker
            :show="showDatePicker"
            v-model="pickerDateValue"
            mode="date"
            title="选择日期"
            @confirm="onDateConfirm"
        />
    </view>
</template>
@@ -65,7 +64,7 @@
import { addMaintain } from '@/api/equipmentManagement/repair';
import useUserStore from "@/store/modules/user";
import dayjs from "dayjs";
import { showToast } from 'vant';
import { formatDateToYMD } from '@/utils/ruoyi'
defineOptions({
    name: "设备维修表单",
@@ -76,8 +75,8 @@
// è¡¨å•引用
const formRef = ref(null);
const loading = ref(false);
const showDate = ref(false);
const currentDate = ref([new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()]);
const showDatePicker = ref(false);
const pickerDateValue = ref(Date.now());; // ä½¿ç”¨æ—¶é—´æˆ³
// è¡¨å•验证规则
const formRules = {
@@ -93,10 +92,17 @@
    maintenanceTime: dayjs().format("YYYY-MM-DD"), // ç»´ä¿®æ—¥æœŸï¼ˆåªæ˜¾ç¤ºæ—¥æœŸï¼‰
});
// è‡ªå®šä¹‰showToast函数
const showToast = (message) => {
  uni.showToast({
    title: message,
    icon: 'none'
  })
};
// æ¸…除表单校验状态
const clearValidate = () => {
    // Vant4中不需要手动清除验证状态,重置表单时会自动清除
    // formRef.value?.clearValidate(); // åˆ é™¤è¿™è¡Œ
    // uview-plus不需要手动清除验证状态,重置表单时会自动清除
};
// é‡ç½®è¡¨å•数据和校验状态
@@ -110,20 +116,16 @@
const resetFormAndValidate = () => {
    resetForm();
    // clearValidate(); // åˆ é™¤è¿™è¡Œï¼ŒVant4会自动处理
};
// æäº¤è¡¨å•
const sendForm = async () => {
const submitForm = async () => {
    try {
        // ä½¿ç”¨Vant4的正确验证方式
        formRef.value?.validate().then(() => {
            // éªŒè¯é€šè¿‡
        // ä½¿ç”¨uview-plus的表单验证方式
        const valid = await formRef.value.validate();
        if (valid) {
            submitFormData();
        }).catch((errors) => {
            // éªŒè¯å¤±è´¥
            showToast('请填写完整信息');
        });
        }
    } catch (e) {
        showToast('表单验证失败');
    }
@@ -178,17 +180,11 @@
    return options.id;
};
// æ˜¾ç¤ºæ—¥æœŸé€‰æ‹©å™¨
const showDatePicker = () => {
    showDate.value = true;
};
// ç¡®è®¤æ—¥æœŸé€‰æ‹©
const onDateConfirm = ({ selectedValues }) => {
    // åªä¿å­˜å¹´æœˆæ—¥ï¼Œä¸åŒ…含时分秒
    form.value.maintenanceTime = selectedValues.join('-');
    currentDate.value = selectedValues;
    showDate.value = false;
const onDateConfirm = (e) => {
    form.value.maintenanceTime = formatDateToYMD(e.value)
    pickerDateValue.value = formatDateToYMD(e.value)
    showDatePicker.value = false;
};
// åˆå§‹åŒ–表单数据
@@ -197,7 +193,6 @@
    form.value.maintenanceName = userStore.nickName || '';
    // è®¾ç½®å½“前日期(只包含年月日)
    form.value.maintenanceTime = dayjs().format('YYYY-MM-DD');
    currentDate.value = [new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()];
};
onShow(() => {
@@ -212,6 +207,7 @@
</script>
<style scoped lang="scss">
@import '@/static/scss/form-common.scss';
.repair-maintain {
    min-height: 100vh;
    background: #f8f9fa;
src/pages/equipmentManagement/upkeep/add.vue
@@ -4,68 +4,66 @@
        <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">
        <u-form ref="formRef" :model="form" :rules="formRules" label-width="110px">
            <!-- åŸºæœ¬ä¿¡æ¯ -->
            <van-cell-group title="基本信息" inset>
                <van-field
                    v-model="deviceNameText"
                    label="设备名称"
            <u-form-item label="设备名称" prop="deviceNameText" required border-bottom>
                <u-input
                    v-model="form.deviceNameText"
                    placeholder="请选择设备名称"
                    :rules="formRules.deviceLedgerId"
                    required
                    readonly
                    @click="showDevicePicker"
                    clearable
                >
                    <template #right-icon>
                        <van-icon name="scan" @click.stop="startScan" class="scan-icon" />
                    </template>
                </van-field>
                <van-field
                />
                <template #right>
                    <u-icon name="scan" @click="startScan" class="scan-icon" />
                </template>
            </u-form-item>
            <u-form-item label="规格型号" prop="deviceModel" border-bottom>
                <u-input
                    v-model="form.deviceModel"
                    label="规格型号"
                    placeholder="请输入规格型号"
                    readonly
                    clearable
                />
                <van-field
            </u-form-item>
            <u-form-item label="计划保养日期" prop="maintenancePlanTime" required border-bottom>
                <u-input
                    v-model="form.maintenancePlanTime"
                    label="计划保养日期"
                    placeholder="请选择计划保养日期"
                    :rules="formRules.maintenancePlanTime"
                    required
                    readonly
                    @click="showDatePicker"
                    clearable
                />
            </van-cell-group>
                <template #right>
                    <u-icon name="arrow-right" @click="showDatePicker" />
                </template>
            </u-form-item>
            
            <!-- æäº¤æŒ‰é’® -->
            <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>
                <u-button class="cancel-btn" @click="goBack">取消</u-button>
                <u-button class="save-btn" @click="sendForm" :loading="loading">保存</u-button>
            </view>
        </van-form>
        </u-form>
        <!-- è®¾å¤‡é€‰æ‹©å™¨ -->
        <van-popup v-model:show="showDevice" position="bottom">
            <van-picker
                :model-value="devicePickerValue"
                :columns="deviceColumns"
                @confirm="onDeviceConfirm"
                @cancel="showDevice = 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>
        <up-action-sheet
            :show="showDevice"
            :actions="deviceActions"
            title="选择设备"
            @select="onDeviceConfirm"
            @close="showDevice = false"
        />
<up-datetime-picker
            :show="showDate"
            v-model="pickerDateValue"
            @confirm="onDateConfirm"
            @cancel="showDate = false"
            mode="date"
        />
    </view>
</template>
@@ -76,23 +74,38 @@
import { getDeviceLedger } from '@/api/equipmentManagement/ledger';
import { addUpkeep, editUpkeep, getUpkeepById } from '@/api/equipmentManagement/upkeep';
import dayjs from "dayjs";
import { formatDateToYMD } from '@/utils/ruoyi';
defineOptions({
    name: "设备保养计划表单",
});
const showToast = (message) => {
  uni.showToast({
    title: message,
    icon: 'none'
  })
}
// è¡¨å•引用
const formRef = ref(null);
const operationType = ref('add');
const loading = ref(false);
const showDevice = ref(false);
const devicePickerValue = ref([]);
const showDate = ref(false);
const pickerDateValue = ref(Date.now());
const currentDate = ref([new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()]);
// è®¾å¤‡é€‰é¡¹
const deviceOptions = ref([]);
const deviceNameText = ref('');
// è½¬æ¢ä¸º action-sheet éœ€è¦çš„æ ¼å¼
const deviceActions = computed(() => {
    return deviceOptions.value.map(item => ({
        text: item.deviceName,
        value: item.id,
        data: item
    }));
});
// æ‰«ç ç›¸å…³çŠ¶æ€
const isScanning = ref(false);
@@ -111,14 +124,6 @@
    maintenancePlanTime: dayjs().format("YYYY-MM-DD"), // è®¡åˆ’保养日期
});
// è®¾å¤‡é€‰æ‹©å™¨åˆ—
const deviceColumns = computed(() => {
    return deviceOptions.value.map(item => ({
        text: item.deviceName,
        value: item.id
    }));
});
// åŠ è½½è®¾å¤‡åˆ—è¡¨
const loadDeviceName = async () => {
    try {
@@ -126,15 +131,6 @@
        deviceOptions.value = data || [];
    } catch (e) {
        showToast('获取设备列表失败');
    }
};
// è®¾ç½®è®¾å¤‡è§„格型号
const setDeviceModel = (id) => {
    const option = deviceOptions.value.find((item) => item.id === id);
    if (option) {
        form.value.deviceModel = option.deviceModel;
        deviceNameText.value = option.deviceName;
    }
};
@@ -151,7 +147,7 @@
                // è®¾ç½®è®¾å¤‡åç§°æ˜¾ç¤º
                const device = deviceOptions.value.find(item => item.id === data.deviceLedgerId);
                if (device) {
                    deviceNameText.value = device.deviceName;
                    form.value.deviceNameText = device.deviceName;
                }
            }
        } catch (e) {
@@ -161,26 +157,6 @@
        // æ–°å¢žæ¨¡å¼
        operationType.value = 'add';
    }
};
// æ¸…除表单校验状态
const clearValidate = () => {
    formRef.value?.clearValidate();
};
// é‡ç½®è¡¨å•数据和校验状态
const resetForm = () => {
    form.value = {
        deviceLedgerId: undefined,
        deviceModel: undefined,
        maintenancePlanTime: dayjs().format("YYYY-MM-DD"),
    };
    deviceNameText.value = '';
};
const resetFormAndValidate = () => {
    resetForm();
    clearValidate();
};
// æ‰«æäºŒç»´ç åŠŸèƒ½
@@ -233,7 +209,7 @@
    if (matchedDevice) {
        // æ‰¾åˆ°åŒ¹é…çš„设备,自动填充
        form.value.deviceLedgerId = matchedDevice.id;
        deviceNameText.value = matchedDevice.deviceName;
        form.value.deviceNameText = matchedDevice.deviceName;
        form.value.deviceModel = matchedDevice.deviceModel;
        showToast('设备信息已自动填充');
    } else {
@@ -248,11 +224,15 @@
};
// ç¡®è®¤è®¾å¤‡é€‰æ‹©
const onDeviceConfirm = ({ selectedValues, selectedOptions }) => {
    form.value.deviceLedgerId = selectedOptions[0].value;
    devicePickerValue.value = selectedValues;
const onDeviceConfirm = (selected) => {
    // selected è¿”回的是选中项
    form.value.deviceLedgerId = selected.value;
        form.value.deviceNameText = selected.name;
    const selectedDevice = deviceOptions.value.find(item => item.id === selected.value);
    if (selectedDevice) {
        form.value.deviceModel = selectedDevice.deviceModel;
    }
    showDevice.value = false;
    setDeviceModel(selectedOptions[0].value);
};
// æ˜¾ç¤ºæ—¥æœŸé€‰æ‹©å™¨
@@ -261,9 +241,8 @@
};
// ç¡®è®¤æ—¥æœŸé€‰æ‹©
const onDateConfirm = ({ selectedValues }) => {
    form.value.maintenancePlanTime = selectedValues.join('-');
    currentDate.value = selectedValues;
const onDateConfirm = (e) => {
    form.value.maintenancePlanTime = formatDateToYMD(e.value);
    showDate.value = false;
};
@@ -289,7 +268,8 @@
const sendForm = async () => {
    try {
        // æ‰‹åŠ¨éªŒè¯è¡¨å•
        await formRef.value?.validate();
        const valid = await formRef.value.validate();
        if (!valid) return;
        
        loading.value = true;
        const id = getPageId();
@@ -350,6 +330,7 @@
</script>
<style scoped lang="scss">
@import '@/static/scss/form-common.scss';
.upkeep-add {
    min-height: 100vh;
    background: #f8f9fa;
src/pages/equipmentManagement/upkeep/index.vue
@@ -33,8 +33,8 @@
              <text class="item-id">设备名称:{{ item.deviceName }}</text>
            </view>
            <view class="status-tag">
              <van-tag v-if="item.status === 1" type="success">完结</van-tag>
              <van-tag v-if="item.status === 0" type="danger">待保养</van-tag>
              <u-tag v-if="item.status === 1" type="success">完结</u-tag>
              <u-tag v-if="item.status === 0" type="error">待保养</u-tag>
            </view>
          </view>
          <up-divider></up-divider>
@@ -67,28 +67,29 @@
            <view class="detail-row">
              <text class="detail-label">保养结果</text>
              <view class="detail-value">
                <van-tag v-if="item.maintenanceResult === 1" type="success">
                  å®Œå¥½
                </van-tag>
                <van-tag v-if="item.maintenanceResult === 0" type="danger">
                  ç»´ä¿®
                </van-tag>
                <text v-if="item.maintenanceResult === undefined || item.maintenanceResult === null">-</text>
              </view>
              <u-tag v-if="item.maintenanceResult === 1" type="success">
                å®Œå¥½
              </u-tag>
              <u-tag v-if="item.maintenanceResult === 0" type="error">
                ç»´ä¿®
              </u-tag>
              <text v-if="item.maintenanceResult === undefined || item.maintenanceResult === null">-</text>
            </view>
            </view>
          </view>
          
          <!-- æŒ‰é’®åŒºåŸŸ -->
          <view class="action-buttons">
            <van-button
            <u-button
              type="primary"
              size="small"
              class="action-btn"
              :disabled="item.status === 1"
              @click.stop="edit(item.id)"
            >
              ç¼–辑
            </van-button>
            <van-button
            </u-button>
            <u-button
              type="warning"
              size="small"
              class="action-btn"
@@ -96,16 +97,16 @@
              @click.stop="addMaintain(item.id)"
            >
              ä¿å…»
            </van-button>
            <van-button
              type="danger"
            </u-button>
            <u-button
              type="error"
              size="small"
              plain
              class="action-btn"
              @click.stop="delUpkeepByIds(item.id)"
            >
              åˆ é™¤
            </van-button>
            </u-button>
          </view>
        </view>
      </view>
@@ -115,12 +116,10 @@
      <text>暂无设备保养数据</text>
    </view>
    
    <!-- æµ®åŠ¨æ°”æ³¡æŒ‰é’® -->
    <van-floating-bubble
      axis="xy"
      icon="plus"
      @click="addPlan"
    />
    <!-- æµ®åŠ¨æ–°å¢žæŒ‰é’® -->
    <view class="fab-button" @click="addPlan">
      <up-icon name="plus" size="24" color="#ffffff"></up-icon>
    </view>
  </view>
</template>
@@ -130,7 +129,13 @@
import PageHeader from '@/components/PageHeader.vue'
import { getUpkeepPage, delUpkeep } from '@/api/equipmentManagement/upkeep'
import useUserStore from "@/store/modules/user"
import { showToast } from 'vant';
// æ˜¾ç¤ºæç¤ºä¿¡æ¯
const showToast = (message) => {
  uni.showToast({
    title: message,
    icon: 'none'
  })
};
import dayjs from "dayjs"
const userStore = useUserStore()
@@ -435,4 +440,19 @@
.action-btn {
  flex: 1;
}
.fab-button {
  position: fixed;
  bottom: calc(30px + env(safe-area-inset-bottom));
  right: 30px;
  width: 56px;
  height: 56px;
  background: #667eea;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 4px 16px rgba(102, 126, 234, 0.3);
  z-index: 1000;
}
</style>
src/pages/equipmentManagement/upkeep/maintain.vue
@@ -4,65 +4,70 @@
        <PageHeader title="新增保养" @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">
        <u-form ref="formRef" :model="form" :rules="formRules" label-width="110px" :error-type="['message']">
            <!-- åŸºæœ¬ä¿¡æ¯ -->
            <van-cell-group title="保养信息" inset>
                <van-field
            <u-form-item label="实际保养人" prop="maintenanceActuallyName" required border-bottom>
                <u-input
                    v-model="form.maintenanceActuallyName"
                    label="实际保养人"
                    placeholder="请输入实际保养人"
                    :rules="formRules.maintenanceActuallyName"
                    required
                    clearable
                />
                <van-field
            </u-form-item>
            <u-form-item label="实际保养日期" prop="maintenanceActuallyTime" required border-bottom>
                <u-input
                    v-model="form.maintenanceActuallyTime"
                    label="实际保养日期"
                    placeholder="请选择实际保养日期"
                    :rules="formRules.maintenanceActuallyTime"
                    required
                    readonly
                    @click="showDatePicker"
                    clearable
                />
                <van-field
                <template #right>
                    <u-icon name="arrow-right" @click.stop="showDatePicker" />
                </template>
            </u-form-item>
            <u-form-item label="保养结果" prop="maintenanceResult" required border-bottom>
                <u-input
                    v-model="maintenanceResultText"
                    label="保养结果"
                    placeholder="请选择保养结果"
                    :rules="formRules.maintenanceResult"
                    required
                    readonly
                    @click="showResultPicker"
                    clearable
                />
            </van-cell-group>
                <template #right>
                    <u-icon name="arrow-right" @click.stop="showResultPicker" />
                </template>
            </u-form-item>
            
            <!-- æäº¤æŒ‰é’® -->
            <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>
                <u-button class="cancel-btn" @click="goBack">取消</u-button>
                <u-button class="save-btn" @click="sendForm" :loading="loading">保存</u-button>
            </view>
        </van-form>
        </u-form>
        <!-- æ—¥æœŸé€‰æ‹©å™¨ -->
        <van-popup v-model:show="showDate" position="bottom">
            <van-date-picker
                v-model="currentDate"
        <u-popup v-model="showDate" mode="bottom" :closeable="true">
            <u-datetime-picker
                v-model="form.maintenanceActuallyTime"
                mode="date"
                title="选择日期"
                @confirm="onDateConfirm"
                @cancel="showDate = false"
            />
        </van-popup>
        </u-popup>
        <!-- ä¿å…»ç»“果选择器 -->
        <van-popup v-model:show="showResult" position="bottom">
            <van-picker
                :model-value="resultPickerValue"
        <u-popup v-model="showResult" mode="bottom" :closeable="true">
            <view class="popup-title">选择保养结果</view>
            <u-picker
                v-model="resultPickerValue"
                :columns="resultColumns"
                @confirm="onResultConfirm"
                @cancel="showResult = false"
            />
        </van-popup>
        </u-popup>
    </view>
</template>
@@ -73,7 +78,15 @@
import { addMaintenance } from '@/api/equipmentManagement/upkeep';
import useUserStore from "@/store/modules/user";
import dayjs from "dayjs";
import { showToast } from 'vant';
import { formatDateToYMD } from '@/utils/ruoyi';
// æ˜¾ç¤ºæç¤ºä¿¡æ¯
const showToast = (message) => {
  uni.showToast({
    title: message,
    icon: 'none'
  })
};
defineOptions({
    name: "设备保养表单",
@@ -112,8 +125,7 @@
// æ¸…除表单校验状态
const clearValidate = () => {
    // Vant4中不需要手动清除验证状态,重置表单时会自动清除
    // formRef.value?.clearValidate(); // åˆ é™¤è¿™è¡Œ
    // uview-plus不需要手动清除验证状态,重置表单时会自动清除
};
// é‡ç½®è¡¨å•数据和校验状态
@@ -134,14 +146,11 @@
// æäº¤è¡¨å•
const sendForm = async () => {
    try {
        // ä½¿ç”¨Vant4的正确验证方式
        formRef.value?.validate().then(() => {
            // éªŒè¯é€šè¿‡
            submitFormData();
        }).catch((errors) => {
            // éªŒè¯å¤±è´¥
            showToast('请填写完整信息');
        });
        // ä½¿ç”¨uview-plus的表单验证方式
        const valid = await formRef.value.validate();
        if (!valid) return;
        // éªŒè¯é€šè¿‡
        submitFormData();
    } catch (e) {
        showToast('表单验证失败');
    }
@@ -202,10 +211,9 @@
};
// ç¡®è®¤æ—¥æœŸé€‰æ‹©
const onDateConfirm = ({ selectedValues }) => {
const onDateConfirm = (e) => {
    // åªä¿å­˜å¹´æœˆæ—¥ï¼Œä¸åŒ…含时分秒
    form.value.maintenanceActuallyTime = selectedValues.join('-');
    currentDate.value = selectedValues;
    form.value.maintenanceActuallyTime = formatDateToYMD(e.value);
    showDate.value = false;
};
@@ -215,10 +223,10 @@
};
// ç¡®è®¤ä¿å…»ç»“果选择
const onResultConfirm = ({ selectedValues, selectedOptions }) => {
    form.value.maintenanceResult = selectedOptions[0].value;
    maintenanceResultText.value = selectedOptions[0].text;
    resultPickerValue.value = selectedValues;
const onResultConfirm = ({ selectedIndex, selectedValue, selectedLabel }) => {
    form.value.maintenanceResult = selectedValue;
    maintenanceResultText.value = selectedLabel;
    resultPickerValue.value = selectedValue;
    showResult.value = false;
};
src/pages/index.vue
@@ -1,9 +1,9 @@
<template>
  <view class="content">
    <view class="content">
        <view class="header-section">
            <view class="currentFactory">
                <up-text type="primary" :text="userStore.currentFactoryName" @click="show = true" size="18"
                             class="factoryName" suffixIcon="arrow-right" :iconStyle="iconStyle"></up-text>
                                 class="factoryName" suffixIcon="arrow-right" :iconStyle="iconStyle"></up-text>
            </view>
            <up-picker :show="show" :columns="factoryList" @confirm="changeFactory" @cancel="show = false"></up-picker>
        </view>
@@ -18,20 +18,20 @@
            </view>
        </view>
        
<!--        <view class="notice-section">-->
<!--            <view class="notice">-->
<!--                <view class="notice-content">-->
<!--                    <view class="notice-left">-->
<!--                        <text class="notice-status">通知</text>-->
<!--                    </view>-->
<!--                    <view class="notice-separator"></view>-->
<!--                    <view class="notice-right">-->
<!--                        <text class="notice-label">{{currentStatus}}</text>-->
<!--                        <text class="notice-text">当日销售设备数:<text class="notice-number">{{number}}<text class="notice-unit">个</text></text></text>-->
<!--                    </view>-->
<!--                </view>-->
<!--            </view>-->
<!--        </view>-->
        <!--        <view class="notice-section">-->
        <!--            <view class="notice">-->
        <!--                <view class="notice-content">-->
        <!--                    <view class="notice-left">-->
        <!--                        <text class="notice-status">通知</text>-->
        <!--                    </view>-->
        <!--                    <view class="notice-separator"></view>-->
        <!--                    <view class="notice-right">-->
        <!--                        <text class="notice-label">{{currentStatus}}</text>-->
        <!--                        <text class="notice-text">当日销售设备数:<text class="notice-number">{{number}}<text class="notice-unit">个</text></text></text>-->
        <!--                    </view>-->
        <!--                </view>-->
        <!--            </view>-->
        <!--        </view>-->
        
        <!-- è¥é”€ç®¡ç†æ¨¡å— -->
        <view class="common-module marketing-module">
@@ -152,7 +152,7 @@
                </up-grid>
            </view>
        </view>
  </view>
    </view>
</template>
<script setup>
@@ -456,7 +456,7 @@
        right: 0;
        bottom: 0;
        background: radial-gradient(circle at 20% 80%, rgba(41, 121, 255, 0.02) 0%, transparent 50%),
                    radial-gradient(circle at 80% 20%, rgba(156, 39, 176, 0.02) 0%, transparent 50%);
        radial-gradient(circle at 80% 20%, rgba(156, 39, 176, 0.02) 0%, transparent 50%);
        pointer-events: none;
        z-index: -1;
    }
@@ -608,11 +608,11 @@
@keyframes shine {
    0% {
        transform: translateX(-100%) translateY(-100%) rotate(45deg);
    0% {
        transform: translateX(-100%) translateY(-100%) rotate(45deg);
    }
    100% {
        transform: translateX(100%) translateY(100%) rotate(45deg);
    100% {
        transform: translateX(100%) translateY(100%) rotate(45deg);
    }
}
src/pages/login.vue
@@ -1,17 +1,17 @@
<template>
  <view class="normal-login-container">
    <view class="logo-content">
      <text>账号密码登录</text>
    </view>
    <view class="login-form-content">
      <view class="input-item flex align-center">
    <view class="normal-login-container">
        <view class="logo-content">
            <text>账号密码登录</text>
        </view>
        <view class="login-form-content">
            <view class="input-item flex align-center">
                <up-input prefixIcon="account" placeholder="请输入账号" border="bottom"
                                    @blur="getUserLoginFacotryList"
                                    maxlength="30" v-model="loginForm.username" clearable></up-input>
      </view>
      <view class="input-item flex align-center">
            </view>
            <view class="input-item flex align-center">
                <up-input prefixIcon="lock" placeholder="请输入密码" border="bottom" maxlength="20" v-model="loginForm.password" clearable type="password"></up-input>
      </view>
            </view>
            <view class="input-item flex align-center select-container">
                <up-icon name="tags" size="18"></up-icon>
                <up-picker-data
@@ -22,10 +22,10 @@
                    labelKey="name">
                </up-picker-data>
            </view>
      <view>
        <button @click="handleLogin" class="login-btn cu-btn block bg-blue lg round">登录</button>
      </view>
    </view>
            <view>
                <button @click="handleLogin" class="login-btn cu-btn block bg-blue lg round">登录</button>
            </view>
        </view>
        <!-- è®°ä½å¯†ç é€‰é¡¹ -->
        <view class="remember-password">
            <up-checkbox
@@ -37,7 +37,7 @@
            >
            </up-checkbox>
        </view>
  </view>
    </view>
</template>
<script setup>
@@ -52,8 +52,8 @@
const useWxLogin = ref(false); // æ˜¯å¦ä½¿ç”¨å¾®ä¿¡ç™»å½•
const rememberPassword = ref(false); // è®°ä½å¯†ç 
const loginForm = ref({
  username: "",
  password: "",
    username: "",
    password: "",
    factoryId: "",
    currentFatoryName: "",
});
@@ -61,43 +61,43 @@
// ä¿å­˜å¯†ç åˆ°æœ¬åœ°å­˜å‚¨
function savePassword() {
  if (rememberPassword.value) {
    uni.setStorageSync('remembered_username', loginForm.value.username);
    uni.setStorageSync('remembered_password', loginForm.value.password);
    uni.setStorageSync('remember_password', true);
  } else {
    uni.removeStorageSync('remembered_username');
    uni.removeStorageSync('remembered_password');
    uni.setStorageSync('remember_password', false);
  }
    if (rememberPassword.value) {
        uni.setStorageSync('remembered_username', loginForm.value.username);
        uni.setStorageSync('remembered_password', loginForm.value.password);
        uni.setStorageSync('remember_password', true);
    } else {
        uni.removeStorageSync('remembered_username');
        uni.removeStorageSync('remembered_password');
        uni.setStorageSync('remember_password', false);
    }
}
// ä»Žæœ¬åœ°å­˜å‚¨åŠ è½½å¯†ç 
function loadPassword() {
  const remembered = uni.getStorageSync('remember_password');
  if (remembered) {
    rememberPassword.value = true;
    const savedUsername = uni.getStorageSync('remembered_username');
    const savedPassword = uni.getStorageSync('remembered_password');
    if (savedUsername) {
      loginForm.value.username = savedUsername;
    }
    if (savedPassword) {
      loginForm.value.password = savedPassword;
    }
  }
    const remembered = uni.getStorageSync('remember_password');
    if (remembered) {
        rememberPassword.value = true;
        const savedUsername = uni.getStorageSync('remembered_username');
        const savedPassword = uni.getStorageSync('remembered_password');
        if (savedUsername) {
            loginForm.value.username = savedUsername;
        }
        if (savedPassword) {
            loginForm.value.password = savedPassword;
        }
    }
}
if (useWxLogin.value) {
  getWxCode().then(res => {
    console.log(res);
    wxLogin('miniapp',res).then(res => {
      if(res.token != null){
        setToken(res.token);
        loginSuccess()
      }
    });
  })
    getWxCode().then(res => {
        console.log(res);
        wxLogin('miniapp',res).then(res => {
            if(res.token != null){
                setToken(res.token);
                loginSuccess()
            }
        });
    })
}
function getUserLoginFacotryList() {
@@ -124,36 +124,36 @@
}
async function handleLogin() {
  if (loginForm.value.username === "") {
    modal.msgError("请输入您的账号")
  } else if (loginForm.value.password === "") {
    modal.msgError("请输入您的密码")
  } else if (loginForm.value.factoryId === "") {
    modal.msgError("请选择公司")
  } else {
    modal.loading("登录中,请耐心等待...")
    pwdLogin()
  }
    if (loginForm.value.username === "") {
        modal.msgError("请输入您的账号")
    } else if (loginForm.value.password === "") {
        modal.msgError("请输入您的密码")
    } else if (loginForm.value.factoryId === "") {
        modal.msgError("请选择公司")
    } else {
        modal.loading("登录中,请耐心等待...")
        pwdLogin()
    }
};
// å¯†ç ç™»å½•
async function pwdLogin() {
  userStore.loginCheckFactory(loginForm.value).then(() => {
    modal.closeLoading()
    // ç™»å½•成功后保存密码
    savePassword();
    loginSuccess()
  }).catch(() => {
    userStore.loginCheckFactory(loginForm.value).then(() => {
        modal.closeLoading()
  })
        // ç™»å½•成功后保存密码
        savePassword();
        loginSuccess()
    }).catch(() => {
        modal.closeLoading()
    })
};
function loginSuccess(result) {
  // è®¾ç½®ç”¨æˆ·ä¿¡æ¯
  userStore.getInfo().then(res => {
    uni.switchTab({
      url: '/pages/index'
    });
  })
    // è®¾ç½®ç”¨æˆ·ä¿¡æ¯
    userStore.getInfo().then(res => {
        uni.switchTab({
            url: '/pages/index'
        });
    })
}
// é¡µé¢åŠ è½½æ—¶æ£€æŸ¥æ˜¯å¦æœ‰ä¿å­˜çš„å¯†ç 
onMounted(() => {
@@ -164,55 +164,55 @@
<style lang="scss">
page {
  background-color: #ffffff;
    background-color: #ffffff;
}
.normal-login-container {
  width: 100%;
    width: 100%;
    height: 100vh;
  .logo-content {
    width: 90%;
    .logo-content {
        width: 90%;
        font-weight: 400;
        font-size: 30px;
        color: #333333;
        margin: 80px 0 0 30px;
    image {
      border-radius: 4px;
    }
    .title {
      margin-left: 10px;
    }
  }
        image {
            border-radius: 4px;
        }
        .title {
            margin-left: 10px;
        }
    }
    .u-checkbox {
        margin-left: 34px;
    }
  .login-form-content {
    text-align: center;
    margin: 58px auto;
    .login-form-content {
        text-align: center;
        margin: 58px auto;
        padding: 0 30px;
        .input-item {
       margin: 30px auto;
       height: 45px;
       .icon {
         font-size: 38rpx;
         margin-left: 10px;
         color: #999;
       }
       .input {
         width: 100%;
         font-size: 14px;
         line-height: 20px;
         text-align: left;
         padding-left: 15px;
       }
     }
            margin: 30px auto;
            height: 45px;
            .icon {
                font-size: 38rpx;
                margin-left: 10px;
                color: #999;
            }
            .input {
                width: 100%;
                font-size: 14px;
                line-height: 20px;
                text-align: left;
                padding-left: 15px;
            }
        }
        .select-container {
            flex: 1;
            border-bottom: 1px solid #e5e5e5;
@@ -233,31 +233,31 @@
                }
            }
        }
    .login-btn {
      margin-top: 60px;
      height: 50px;
        .login-btn {
            margin-top: 60px;
            height: 50px;
            background: linear-gradient( 140deg, #00BAFF 0%, #006CFB 100%);
            box-shadow: 0px 4px 10px 0px rgba(3,88,185,0.2);
            border-radius: 40px 40px 40px 40px;
    }
    .xieyi {
      color: #333;
      margin-top: 20px;
    }
    .login-code {
      height: 38px;
      float: right;
      .login-code-img {
        height: 38px;
        position: absolute;
        margin-left: 10px;
        width: 200rpx;
      }
    }
  }
        }
        .xieyi {
            color: #333;
            margin-top: 20px;
        }
        .login-code {
            height: 38px;
            float: right;
            .login-code-img {
                height: 38px;
                position: absolute;
                margin-left: 10px;
                width: 200rpx;
            }
        }
    }
}
</style>
src/pages/procurementManagement/invoiceEntry/add.vue
@@ -6,118 +6,188 @@
        <!-- è¡¨å•内容 -->
        <u-form @submit="submitForm" ref="formRef" label-width="110" input-align="right" error-message-align="right">
            <!-- åŸºæœ¬ä¿¡æ¯ -->
            <u-cell-group title="基本信息">
                <u-form-item label="采购合同号" prop="contractNo" required border-bottom>
                    <u-input v-model="form.contractNo" placeholder="请输入采购合同号" clearable />
            <view class="form-section">
                <u-form-item label="采购合同号" prop="purchaseLedgerNo" required>
                    <u-input v-model="form.purchaseLedgerNo" placeholder="自动填充" disabled />
                </u-form-item>
                <u-form-item label="供应商名称" prop="supplierName" required border-bottom>
                    <u-input v-model="form.supplierName" placeholder="请输入供应商名称" clearable />
                <u-form-item label="销售合同号" prop="salesContractNo" required>
                    <u-input v-model="form.salesContractNo" placeholder="自动填充" disabled />
                </u-form-item>
                <u-form-item label="发票号" prop="invoiceNo" required border-bottom>
                    <u-input v-model="form.invoiceNo" placeholder="请输入发票号" clearable />
                <u-form-item label="供应商名称" prop="supplierName" required>
                    <u-input v-model="form.supplierName" placeholder="自动填充" disabled />
                </u-form-item>
                <u-form-item label="发票金额" prop="invoiceAmount" required border-bottom>
                    <u-input v-model="form.invoiceAmount" type="number" placeholder="请输入发票金额" clearable />
                <u-form-item label="项目名称" prop="projectName" required>
                    <u-input v-model="form.projectName" placeholder="自动填充" disabled />
                </u-form-item>
                <u-form-item label="税率" prop="taxRate" required border-bottom>
                    <u-input v-model="form.taxRate" placeholder="请输入税率" clearable />
                <u-form-item label="发票号" prop="invoiceNumber" required>
                    <u-input v-model="form.invoiceNumber" placeholder="请输入" clearable />
                </u-form-item>
                <u-form-item label="开票日期" prop="issueDate" required border-bottom>
                    <u-input v-model="form.issueDate" placeholder="请选择开票日期" readonly @click="showIssueDatePicker = true" clearable />
                <u-form-item label="发票金额(元)" prop="invoiceAmount" required>
                    <u-input v-model="form.invoiceAmount" type="number" placeholder="自动填充" disabled />
                </u-form-item>
                <u-form-item label="录入人" border-bottom>
                    <u-input v-model="form.recorder" placeholder="自动填充" readonly />
                <u-form-item label="录入人">
                    <u-input v-model="form.issUer" placeholder="自动填充" disabled />
                </u-form-item>
                <u-form-item label="创建时间" border-bottom>
                    <u-input v-model="form.createTime" placeholder="请选择创建时间" readonly @click="showCreateTimePicker = true" clearable />
                <u-form-item
                    label="开票日期"
                    prop="entryDate"
                    required
                    @click="showEntryDatePicker = true"
                >
                    <u-input
                        v-model="form.entryDate"
                        placeholder="请选择开票日期"
                        readonly
                        @click="showEntryDatePicker = true"
                    />
                    <template #right>
                        <u-icon name="arrow-right" @click="showEntryDatePicker = true"></u-icon>
                    </template>
                </u-form-item>
            </u-cell-group>
            <!-- äº§å“ä¿¡æ¯ -->
            <view class="product-section" v-if="!productData || productData.length === 0">
                <u-empty description="暂无产品数据" />
                <u-form-item
                    label="录入日期"
                    prop="enterDate"
                    required
                    @click="showEnterDatePicker = true"
                >
                    <u-input
                        v-model="form.enterDate"
                        placeholder="请选择录入日期"
                        readonly
                        @click="showEnterDatePicker = true"
                    />
                    <template #right>
                        <u-icon name="arrow-right" @click="showEnterDatePicker = true"></u-icon>
                    </template>
                </u-form-item>
            </view>
            
            <!-- äº§å“åˆ—表 -->
            <view class="product-list" v-if="productData && productData.length > 0">
                <view class="product-card" v-for="(product, idx) in productData" :key="idx">
                    <view class="product-header">
                        <view class="product-title">
                            <u-icon name="file-text" color="#2979ff" size="15" />
                            <text class="product-name">产品 {{ idx + 1 }}</text>
                        </view>
            <!-- äº§å“ä¿¡æ¯ -->
            <view class="product-section">
                <view class="section-header">
                    <view>
                        <text class="section-title">产品信息</text>
                    </view>
                    <!-- äº§å“ä¿¡æ¯è¡¨å• -->
                    <view class="product-form">
                        <u-form-item label="产品名称" border-bottom>
                            <u-input v-model="product.productName" placeholder="请输入产品名称" />
                        </u-form-item>
                        <u-form-item label="规格型号" border-bottom>
                            <u-input v-model="product.specification" placeholder="请输入规格型号" />
                        </u-form-item>
                        <u-form-item label="单位" border-bottom>
                            <u-input v-model="product.unit" placeholder="请输入单位" />
                        </u-form-item>
                        <u-form-item label="数量" border-bottom>
                            <u-input v-model="product.quantity" type="number" placeholder="请输入数量" />
                        </u-form-item>
                        <u-form-item label="单价" border-bottom>
                            <u-input v-model="product.unitPrice" type="number" placeholder="请输入单价" />
                        </u-form-item>
                        <u-form-item label="金额" border-bottom>
                            <u-input v-model="product.amount" type="number" placeholder="请输入金额" />
                        </u-form-item>
                        <u-form-item label="税率" border-bottom>
                            <u-input v-model="product.taxRate" placeholder="请输入税率" />
                        </u-form-item>
                        <u-form-item label="税额" border-bottom>
                            <u-input v-model="product.taxAmount" type="number" placeholder="请输入税额" />
                        </u-form-item>
                        <u-form-item label="含税金额" border-bottom>
                            <u-input v-model="product.totalAmount" type="number" placeholder="请输入含税金额" />
                        </u-form-item>
                        <u-form-item label="备注" border-bottom>
                            <u-textarea v-model="product.remark" placeholder="请输入备注" :maxlength="200" count :autoHeight="true" />
                        </u-form-item>
                </view>
                <view v-if="!productData || productData.length === 0" class="empty-state">
                    <view class="empty-text">暂无产品数据</view>
                </view>
                <view v-else class="product-list">
                    <view
                        v-for="(item, index) in productData"
                        :key="index"
                        class="product-card"
                    >
                        <!-- äº§å“å¤´éƒ¨ -->
                        <view class="product-header">
                            <view class="product-title">
                                <view class="document-icon">
                                    <u-icon name="file-text" size="16" color="#ffffff"></u-icon>
                                </view>
                                <text class="product-productCategory">产品 {{ index + 1 }}</text>
                            </view>
                        </view>
                        <!-- äº§å“ä¿¡æ¯è¡¨å• -->
                        <view class="product-form">
                            <u-form-item label="产品大类" prop="productCategory" border-bottom>
                                <u-input v-model="item.productCategory" placeholder="" disabled />
                            </u-form-item>
                            <u-form-item label="规格型号" prop="specificationModel" border-bottom>
                                <u-input v-model="item.specificationModel" placeholder="" disabled />
                            </u-form-item>
                            <u-form-item label="单位" prop="unit" border-bottom>
                                <u-input v-model="item.unit" placeholder="" disabled />
                            </u-form-item>
                            <u-form-item label="数量" prop="quantity" border-bottom>
                                <u-input v-model="item.quantity" placeholder="" disabled />
                            </u-form-item>
                            <u-form-item label="税率(%)" prop="taxRate" border-bottom>
                                <u-input v-model="item.taxRate" placeholder="" disabled />
                            </u-form-item>
                            <u-form-item label="录入日期" prop="registerDate" border-bottom>
                                <u-input v-model="item.registerDate" placeholder="" disabled />
                            </u-form-item>
                            <u-form-item label="含税单价(元)" prop="taxInclusiveUnitPrice" border-bottom>
                                <u-input v-model="item.taxInclusiveUnitPrice" placeholder="" disabled />
                            </u-form-item>
                            <u-form-item label="含税总价(元)" prop="taxInclusiveTotalPrice" border-bottom>
                                <u-input v-model="item.taxInclusiveTotalPrice" placeholder="" disabled />
                            </u-form-item>
                            <u-form-item label="不含税总价(元)" prop="taxExclusiveTotalPrice" border-bottom>
                                <u-input v-model="item.taxExclusiveTotalPrice" placeholder="" disabled />
                            </u-form-item>
                            <!-- æœ¬æ¬¡æ¥ç¥¨ä¿¡æ¯ -->
                            <u-form-item label="本次来票数" prop="ticketsNum" border-bottom>
                                <u-input
                                    v-model="item.ticketsNum"
                                    type="number"
                                    placeholder="请输入来票数量"
                                    @blur="invoiceNumBlur(item)"
                                />
                            </u-form-item>
                            <u-form-item label="本次来票金额(元)" prop="ticketsAmount" border-bottom>
                                <u-input
                                    v-model="item.ticketsAmount"
                                    type="number"
                                    placeholder="请输入来票金额"
                                    @blur="invoiceAmountBlur(item)"
                                />
                            </u-form-item>
                            <!-- æœªæ¥ç¥¨ä¿¡æ¯ -->
                            <u-form-item label="未来票数" prop="futureTickets" border-bottom>
                                <u-input v-model="item.futureTickets" placeholder="" disabled />
                            </u-form-item>
                            <u-form-item label="未来票金额(元)" prop="futureTicketsAmount" border-bottom>
                                <u-input v-model="item.futureTicketsAmount" placeholder="" disabled />
                            </u-form-item>
                        </view>
                    </view>
                </view>
            </view>
            
            <!-- æäº¤æŒ‰é’® -->
            <view class="footer-btns">
                <u-button class="cancel-btn" @click="goBack">取消</u-button>
                <u-button class="save-btn" type="primary" @click="submitForm">保存</u-button>
            </view>
            <!-- ä½¿ç”¨FooterButtons组件 -->
            <FooterButtons
                @cancel="goBack"
                @confirm="submitForm"
                :loading="submitting"
            />
            <!-- ä¸ºåº•部按钮留出空间 -->
            <view style="height: 80px;"></view>
        </u-form>
        <!-- å¼€ç¥¨æ—¥æœŸé€‰æ‹©å™¨ -->
        <u-popup v-model="showIssueDatePicker" mode="bottom">
        <u-popup :show="showEntryDatePicker" mode="bottom" @close="showEntryDatePicker = false">
            <u-datetime-picker
                v-model="issueDateValue"
                title="选择开票日期"
                @confirm="onIssueDateConfirm"
                @cancel="showIssueDatePicker = false"
                :show="true"
                v-model="entryDateValue"
                @confirm="onEntryDateConfirm"
                @cancel="showEntryDatePicker = false"
                mode="date"
            />
        </u-popup>
        <!-- åˆ›å»ºæ—¶é—´é€‰æ‹©å™¨ -->
        <u-popup v-model="showCreateTimePicker" mode="bottom">
        <!-- å½•入日期选择器 -->
        <u-popup :show="showEnterDatePicker" mode="bottom" @close="showEnterDatePicker = false">
            <u-datetime-picker
                v-model="createTimeValue"
                title="选择创建时间"
                @confirm="onCreateTimeConfirm"
                @cancel="showCreateTimePicker = false"
                :show="true"
                v-model="enterDateValue"
                @confirm="onEnterDateConfirm"
                @cancel="showEnterDatePicker = false"
                mode="date"
            />
        </u-popup>
    </view>
</template>
<script setup>
// æ›¿æ¢ Vant çš„ toast æ–¹æ³•
// import { showToast, showLoadingToast, closeToast } from 'vant'
// æ›¿æ¢ toast æ–¹æ³•
import { ref, onMounted } from 'vue'
import FooterButtons from '@/components/FooterButtons.vue'
const showToast = (message) => {
  uni.showToast({
    title: message,
@@ -134,108 +204,262 @@
const closeToast = () => {
  uni.hideLoading()
}
import useUserStore from '@/store/modules/user'
import {addOrUpdateRegistration, getPurchaseNoById} from "@/api/procurementManagement/invoiceEntry";
import {getInfo} from "@/api/procurementManagement/invoiceEntry.js";
import { formatDateToYMD } from '@/utils/ruoyi'
const userStore = useUserStore()
const editData = ref(null);
// è¡¨å•引用
const formRef = ref()
// è¡¨å•数据
let form = ref({
  purchaseLedgerNo: '',
  salesContractNo: '',
  supplierName: '',
  projectName: '',
  issUer: '',
  entryDate: '',
  enterDate: '',
  invoiceAmount: '',
  invoiceNumber: '',
  fileIds: []
})
// äº§å“æ•°æ®
const productData = ref([])
// æ–‡ä»¶ä¸Šä¼ ç›¸å…³
const action = ref('/dev-api/common/upload')
const getToken = () => {
  return userStore.token || ''
}
// æ—¥æœŸé€‰æ‹©å™¨çŠ¶æ€
const showEntryDatePicker = ref(false)
const showEnterDatePicker = ref(false)
const entryDateValue = ref(Date.now())
const enterDateValue = ref(Date.now())
// æäº¤çŠ¶æ€
const submitting = ref(false)
// è¿”回上一页
const goBack = () => {
    // æ¸…理本地存储的数据
    uni.removeStorageSync('editData');
  uni.navigateBack()
}
// æ ¼å¼åŒ–æ•°å­—
const formatNumber = (value, precision = 2) => {
  if (!value && value !== 0) return '0.00'
  return Number(value).toFixed(precision)
}
// æ›´æ–°æœªæ¥ç¥¨æ•°æ®
const updateFutureTicketData = (row) => {
  const totalQuantity = parseFloat(row.quantity) || 0
  const currentTicketNum = parseFloat(row.ticketsNum) || 0
  const totalAmount = parseFloat(row.taxInclusiveTotalPrice) || 0
  const currentTicketAmount = parseFloat(row.ticketsAmount) || 0
  row.futureTickets = Math.max(0, totalQuantity - currentTicketNum).toFixed(2)
  row.futureTicketsAmount = Math.max(0, totalAmount - currentTicketAmount).toFixed(2)
}
// æ¥ç¥¨æ•°é‡å˜åŒ–处理
const invoiceNumBlur = (row) => {
    if (!row.ticketsNum || row.ticketsNum === "") {
    row.ticketsNum = 0;
  }
  if (Number(row.ticketsNum) > Number(row.tempFutureTickets)) {
    showToast("本次开票数不得大于未开票数");
    row.ticketsNum = 0;
    return;
  }
  // è®¡ç®—本次来票金额
  row.ticketsAmount = row.ticketsNum * row.taxInclusiveUnitPrice;
  // è®¡ç®—未来票数
  row.futureTickets = row.tempFutureTickets - row.ticketsNum;
  // è®¡ç®—未来票金额
  row.futureTicketsAmount = row.tempFutureTicketsAmount - row.ticketsAmount;
  calculateinvoiceAmount();
}
// æ¥ç¥¨é‡‘额变化处理
const invoiceAmountBlur = (row) => {
    if (!row.ticketsAmount) {
        row.ticketsAmount = 0;
    }
    // è®¡ç®—是否超过来票总金额
    if (Number(row.ticketsAmount) > Number(row.taxInclusiveTotalPrice)) {
        showToast('本次来票金额不得大于含税总金额');
        row.ticketsAmount = 0;
    }
    // è®¡ç®—本次来票数
    row.ticketsNum = Number(
        (row.ticketsAmount / row.taxInclusiveUnitPrice).toFixed(2)
    );
    // è®¡ç®—未来票数和未来票金额
    updateFutureTicketData(row)
    calculateinvoiceAmount();
}
const calculateinvoiceAmount = () => {
    let invoiceAmountTotal = 0;
    productData.value.forEach((item) => {
        if (item.ticketsAmount) {
            invoiceAmountTotal += Number(item.ticketsAmount);
        }
    });
    form.value.invoiceAmount = invoiceAmountTotal.toFixed(2);
}
// æ–‡ä»¶ä¸Šä¼ æˆåŠŸå›žè°ƒ
const uploadSuccess = (res) => {
  if (res && res.data && res.data.fileId) {
    form.value.fileIds.push(res.data.fileId)
  }
}
// æ–‡ä»¶åˆ é™¤å›žè°ƒ
const removeFile = (index, file) => {
  if (file.fileId) {
    const fileIndex = form.value.fileIds.indexOf(file.fileId)
    if (fileIndex > -1) {
      form.value.fileIds.splice(fileIndex, 1)
    }
  }
}
// å¼€ç¥¨æ—¥æœŸç¡®è®¤
const onEntryDateConfirm = (e) => {
    form.value.entryDate = formatDateToYMD(e.value)
    entryDateValue.value = e.value
    showEntryDatePicker.value = false;
};
// å½•入日期确认
const onEnterDateConfirm = (e) => {
    form.value.enterDate = formatDateToYMD(e.value)
    enterDateValue.value = e.value
    showEnterDatePicker.value = false;
}
// æ ¼å¼åŒ–日期
const formatDate = (date) => {
  const year = date.getFullYear()
  const month = String(date.getMonth() + 1).padStart(2, '0')
  const day = String(date.getDate()).padStart(2, '0')
  return `${year}-${month}-${day}`
}
// èŽ·å–äº§å“åˆ—è¡¨
const getProductList = async () => {
  try {
    showLoadingToast('加载中...')
    const res = await getPurchaseNoById({ id: editData.value.id })
        form.value.purchaseLedgerNo = res.data.purchaseContractNumber;
        form.value.invoiceAmount = res.data.invoiceAmount;
        form.value.invoiceNumber = res.data.invoiceNumber;
        const data = await getInfo({ id: editData.value.id });
        productData.value = data.data.productData;
        form.value.salesContractNo = data.data.salesContractNo;
        form.value.projectName = data.data.projectName;
        form.value.supplierName = data.data.supplierName;
        form.value.productData = data.data.productData;
        // è®¾ç½®é»˜è®¤å½•入人
        form.value.issUer = userStore.nickName
        // è®¾ç½®é»˜è®¤æ—¥æœŸ
        const today = new Date()
        form.value.enterDate = formatDate(today)
        form.value.entryDate = formatDate(today)
    closeToast()
  } catch (error) {
    closeToast()
    showToast('获取产品列表失败')
  }
}
// æäº¤è¡¨å•
const submitForm = async () => {
  try {
    submitting.value = true
    // éªŒè¯å‘票号是否填写
    if (!form.value.invoiceNumber) {
      showToast('请输入发票号')
      return
    }
    // éªŒè¯äº§å“æ•°æ®
    if (productData.value.length === 0) {
      showToast('请先添加产品信息')
      return
    }
    // éªŒè¯æ¥ç¥¨æ•°æ®
    const hasInvoiceData = productData.value.some(item => {
      const num = parseFloat(item.ticketsNum) || 0
      const amount = parseFloat(item.ticketsAmount) || 0
      return num > 0 || amount > 0
    })
    if (!hasInvoiceData) {
      showToast('请至少输入一个产品的来票信息')
      return
    }
    const submitData = {
      ...form.value,
        productData: productData.value
    }
    await addOrUpdateRegistration(submitData)
    showToast('提交成功')
    // è¿”回上一页
    setTimeout(() => {
            goBack()
    }, 800)
  } catch (error) {
    showToast('提交失败,请重试')
  } finally {
    submitting.value = false
  }
}
// é¡µé¢åŠ è½½æ—¶åˆå§‹åŒ–æ•°æ®
onMounted(() => {
  // ä»Žé¡µé¢å‚数或缓存中获取销售合同信息
  const contractInfo = uni.getStorageSync('editData')
  if (contractInfo) {
        editData.value = JSON.parse(contractInfo);
    const contract = JSON.parse(contractInfo)
    form.value.purchaseLedgerNo = contract.purchaseLedgerNo || ''
    form.value.salesContractNo = contract.salesContractNo || ''
    form.value.supplierName = contract.supplierName || ''
    form.value.projectName = contract.projectName || ''
    form.value.invoiceAmount = contract.invoiceAmount || ''
    form.value.invoiceNumber = contract.invoiceNumber || ''
    form.value.purchaseLedgerId = contract.id || ''
    // èŽ·å–äº§å“åˆ—è¡¨
    getProductList()
  }
})
</script>
<style scoped lang="scss">
.account-detail {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 5rem;
}
@import '@/static/scss/form-common.scss';
.empty-state {
  padding: 40px 0;
}
.product-section {
  background: #fff;
  margin-top: 1rem;
  padding: 1rem;
  box-shadow: 0 0.125rem 0.5rem rgba(0,0,0,0.04);
}
.section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1rem;
}
.section-title {
  font-size: 1rem;
  font-weight: 600;
  color: #333;
}
.product-list {
  .product-card {
    background: #FFFFFF;
    box-shadow: 0 0 1.25rem 0 rgba(0,57,117,0.08);
    border-radius: 0.5rem 0.5rem 0.5rem 0.5rem;
    padding: 1rem 0.5rem 0 0.5rem;
    position: relative;
    margin-bottom: 1rem;
  }
}
.product-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 0.5rem 0.75rem 0.5rem;
  border-bottom: 0.0625rem solid #e8e8e8;
}
.product-title {
  display: flex;
  align-items: center;
}
.product-productCategory {
  margin-left: 0.5rem;
  font-size: 0.875rem;
  font-weight: 500;
  color: #333;
}
.product-form {
  margin-bottom: 1rem;
}
.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;
  }
}
</style>
src/pages/procurementManagement/invoiceEntry/index.vue
@@ -62,7 +62,7 @@
                    
                    <!-- æ“ä½œæŒ‰é’®åŒºåŸŸ -->
                    <view class="action-buttons">
                        <van-button
                        <u-button
                            type="primary"
                            size="small"
                            @click="handleAddInvoice(item)"
@@ -70,15 +70,14 @@
                            :disabled="item.unReceiptPaymentAmount == 0"
                        >
                            æ–°å¢žå¼€ç¥¨
                        </van-button>
                        <van-button
                            type="default"
                        </u-button>
                        <u-button
                            size="small"
                            @click="handleViewDetail(item)"
                            class="action-btn"
                        >
                            æŸ¥çœ‹è¯¦æƒ…
                        </van-button>
                        </u-button>
                    </view>
                </view>
            </view>
src/pages/procurementManagement/paymentEntry/add.vue
@@ -6,36 +6,36 @@
    <!-- è¡¨å•内容 -->
    <u-form @submit="onSubmit" ref="formRef" label-width="110" input-align="right" error-message-align="right">
      <!-- åŸºæœ¬ä¿¡æ¯ -->
      <u-cell-group title="基本信息">
        <u-form-item label="采购合同号" border-bottom>
      <u-cell-group title="基本信息" class="form-section">
        <u-form-item label="采购合同号" class="form-item">
          <u-input
            v-model="form.purchaseContractNumber"
            placeholder="自动填充"
            readonly
          />
        </u-form-item>
        <u-form-item label="销售合同号" border-bottom>
        <u-form-item label="销售合同号" class="form-item">
          <u-input
            v-model="form.salesContractNo"
            placeholder="自动填充"
            readonly
          />
        </u-form-item>
        <u-form-item label="供应商名称" border-bottom>
        <u-form-item label="供应商名称" class="form-item">
          <u-input
            v-model="form.supplierName"
            placeholder="自动填充"
            readonly
          />
        </u-form-item>
        <u-form-item label="发票号" border-bottom>
        <u-form-item label="发票号" class="form-item">
          <u-input
            v-model="form.invoiceNumber"
            placeholder="自动填充"
            readonly
          />
        </u-form-item>
        <u-form-item label="发票金额(元)" border-bottom>
        <u-form-item label="发票金额(元)" class="form-item">
          <u-input
            v-model="form.invoiceAmount"
            placeholder="自动填充"
@@ -43,7 +43,7 @@
          />
        </u-form-item>
        <view class="tip-text">待付款金额:{{ currentNoReceiptAmount }} å…ƒ</view>
        <u-form-item label="本次付款金额" prop="currentPaymentAmount" required border-bottom>
        <u-form-item label="本次付款金额" prop="currentPaymentAmount" required class="form-item">
          <u-input
            v-model="form.currentPaymentAmount"
            type="number"
@@ -52,30 +52,42 @@
            clearable
          />
        </u-form-item>
        <u-form-item label="付款形式" prop="paymentMethod" required border-bottom>
        <u-form-item label="付款形式" prop="paymentMethod" required class="form-item">
          <u-input
            v-model="form.paymentMethod"
            placeholder="请选择"
            readonly
            @click="showPaymentTypePicker"
          />
          <template #right>
                    <up-icon
                        name="arrow-right"
                        @click="showPaymentTypePicker"
                    ></up-icon>
                </template>
        </u-form-item>
        <u-form-item label="付款日期" prop="paymentDate" required border-bottom>
        <u-form-item label="付款日期" prop="paymentDate" required class="form-item">
          <u-input
            v-model="form.paymentDate"
            placeholder="请选择"
            readonly
            @click="showDatePicker"
          />
          <template #right>
                    <up-icon
                        name="arrow-right"
                        @click="showDatePicker"
                    ></up-icon>
                </template>
        </u-form-item>
        <u-form-item label="登记人" border-bottom>
        <u-form-item label="登记人" class="form-item">
          <u-input
            v-model="form.registrant"
            placeholder="自动填充"
            readonly
          />
        </u-form-item>
        <u-form-item label="登记日期" prop="registrationtDate" required border-bottom>
        <u-form-item label="登记日期" prop="registrationtDate" required class="form-item">
          <u-input
            v-model="form.registrationtDate"
            placeholder="请选择"
@@ -85,39 +97,40 @@
      </u-cell-group>
      
      <!-- æäº¤æŒ‰é’® -->
      <view class="footer-btns">
        <u-button class="cancel-btn" @click="onClickLeft">取消</u-button>
        <u-button class="save-btn" type="primary" @click="onSubmit" :loading="loading">保存</u-button>
      </view>
      <FooterButtons
        :loading="loading"
        @cancel="onClickLeft"
        @confirm="onSubmit"
      />
    </u-form>
    <!-- ä»˜æ¬¾æ–¹å¼é€‰æ‹©å™¨ -->
    <u-popup v-model="showPaymentType" mode="bottom">
      <u-picker
        v-model="pickerValue"
        :columns="receipt_payment_type"
        @confirm="onPaymentTypeConfirm"
        @cancel="showPaymentType = false"
      />
    </u-popup>
    <up-action-sheet
      :show="showPaymentType"
      title="选择付款方式"
      :actions="receipt_payment_type"
      @select="onPaymentTypeConfirm"
      @close="showPaymentType = false"
    />
    <!-- æ—¥æœŸé€‰æ‹©å™¨ -->
    <u-popup v-model="showDate" mode="bottom">
      <u-datetime-picker
        v-model="currentDate"
        title="选择日期"
        @confirm="onDateConfirm"
        @cancel="showDate = false"
      />
    </u-popup>
     <up-datetime-picker
                    :show="showDate"
                    v-model="form.paymentDate"
                    @confirm="onDateConfirm"
                    @cancel="showDate = false"
                    mode="date"
                />
  </view>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue'
import FooterButtons from '@/components/FooterButtons.vue'
import useUserStore from '@/store/modules/user'
import { useDict } from '@/utils/dict'
import {paymentRegistrationAdd} from "@/api/procurementManagement/paymentEntry";
import { formatDateToYMD } from '@/utils/ruoyi'
// æ›¿æ¢ toast å’Œ notify æ–¹æ³•
const showToast = (message) => {
@@ -142,9 +155,7 @@
// å“åº”式数据
const loading = ref(false)
const showPaymentType = ref(false)
const pickerValue = ref([])
const showDate = ref(false)
const currentDate = ref([new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()])
// è¡¨å•数据
const form = ref({
@@ -170,7 +181,7 @@
// è½¬æ¢å­—典数据格式为选择器需要的格式
const receipt_payment_type = computed(() => {
  return dictReceiptPaymentType.value.map(item => ({
    text: item.label,
    name: item.label,
    value: item.value
  }))
})
@@ -193,10 +204,9 @@
}
// ç¡®è®¤ä»˜æ¬¾æ–¹å¼é€‰æ‹©
const onPaymentTypeConfirm = ({ selectedValues, selectedOptions }) => {
  form.value.receiptPaymentType = selectedOptions[0].value
  form.value.paymentMethod = selectedOptions[0].text
    pickerValue.value = selectedValues;
const onPaymentTypeConfirm = (item) => {
  form.value.receiptPaymentType = item.value
  form.value.paymentMethod = item.name
  showPaymentType.value = false
}
@@ -206,9 +216,8 @@
}
// ç¡®è®¤æ—¥æœŸé€‰æ‹©
const onDateConfirm = ({ selectedValues }) => {
  form.value.paymentDate = selectedValues.join('-')
    currentDate.value = selectedValues
const onDateConfirm = (e) => {
  form.value.paymentDate = formatDateToYMD(e.value)
  showDate.value = false
}
@@ -265,51 +274,5 @@
</script>
<style scoped lang="scss">
.account-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; }
@import '@/static/scss/form-common.scss';
</style>
src/pages/procurementManagement/paymentEntry/edit.vue
@@ -6,56 +6,77 @@
        <!-- è¡¨å•内容 -->
        <u-form @submit="onSubmit" ref="formRef" label-width="110" input-align="right" error-message-align="right">
            <!-- åŸºæœ¬ä¿¡æ¯ -->
            <u-cell-group title="基本信息">
                <u-form-item label="采购合同号" border-bottom>
                    <u-input v-model="form.contractNo" placeholder="自动填充" readonly />
            <u-cell-group title="基本信息" class="form-section">
                                <u-form-item label="采购合同号" class="form-item">
                    <u-input v-model="form.purchaseContractNumber" placeholder="自动填充" readonly />
                </u-form-item>
                <u-form-item label="供应商名称" border-bottom>
                <u-form-item label="销售合同号" class="form-item">
                    <u-input v-model="form.salesContractNo" placeholder="自动填充" readonly />
                </u-form-item>
                                <u-form-item label="供应商名称" class="form-item">
                    <u-input v-model="form.supplierName" placeholder="自动填充" readonly />
                </u-form-item>
                <u-form-item label="发票号" border-bottom>
                    <u-input v-model="form.invoiceNo" placeholder="自动填充" readonly />
                                <u-form-item label="发票号" class="form-item">
                                <u-input v-model="form.invoiceNumber" placeholder="自动填充" readonly />
                            </u-form-item>
                                <u-form-item label="发票金额(元)" class="form-item">
                                <u-input v-model="form.invoiceAmount" placeholder="自动填充" readonly />
                            </u-form-item>
                            <view class="tip-text">待付款金额:{{ currentNoReceiptAmount }} å…ƒ</view>
                                <u-form-item label="本次付款金额" prop="currentPaymentAmount" required class="form-item">
                                <u-input v-model="form.currentPaymentAmount" type="number" placeholder="请输入" @blur="changeNum" clearable />
                            </u-form-item>
                                <u-form-item label="付款形式" prop="paymentMethod" required class="form-item">
                                <u-input v-model="form.paymentMethod" placeholder="请选择" readonly @click="showPaymentTypePicker" />
                    <template #right>
                    <up-icon
                        name="arrow-right"
                        @click="showPaymentTypePicker"
                    ></up-icon>
                </template>
                </u-form-item>
                <u-form-item label="发票金额(元)" border-bottom>
                    <u-input v-model="form.invoiceAmount" placeholder="自动填充" readonly />
                </u-form-item>
                <u-form-item label="本次付款金额" prop="paymentAmount" required border-bottom>
                    <u-input v-model="form.paymentAmount" type="number" placeholder="请输入" @blur="changeNum" clearable />
                </u-form-item>
                <u-form-item label="付款形式" prop="paymentType" required border-bottom>
                    <u-input v-model="form.paymentType" placeholder="请选择" readonly @click="showPaymentTypePicker" />
                </u-form-item>
                <u-form-item label="付款日期" prop="paymentDate" required border-bottom>
                                <u-form-item label="付款日期" prop="paymentDate" required class="form-item">
                    <u-input v-model="form.paymentDate" placeholder="请选择" readonly @click="showDatePicker" />
                    <template #right>
                    <up-icon
                        name="arrow-right"
                        @click="showDatePicker"
                    ></up-icon>
                </template>
                </u-form-item>
            </u-cell-group>
                            <u-form-item label="登记人" class="form-item">
                                <u-input v-model="form.registrant" placeholder="自动填充" readonly />
                            </u-form-item>
                            <u-form-item label="登记日期" prop="registrationtDate" required class="form-item">
                                <u-input v-model="form.registrationtDate" placeholder="请选择" readonly />
                            </u-form-item>
                        </u-cell-group>
            
            <!-- æäº¤æŒ‰é’® -->
            <view class="footer-btns">
                <u-button class="cancel-btn" @click="onClickLeft">取消</u-button>
                <u-button class="save-btn" type="primary" @click="onSubmit" :loading="loading">保存</u-button>
            </view>
            <FooterButtons
                :loading="loading"
                @cancel="onClickLeft"
                @confirm="onSubmit"
            />
        </u-form>
        <!-- ä»˜æ¬¾æ–¹å¼é€‰æ‹©å™¨ -->
        <u-popup v-model="showPaymentType" mode="bottom">
            <u-picker
                v-model="pickerValue"
                :columns="paymentTypeOptions"
                @confirm="onPaymentTypeConfirm"
                @cancel="showPaymentType = false"
            />
        </u-popup>
        <up-action-sheet
      :show="showPaymentType"
      title="选择付款方式"
      :actions="receipt_payment_type"
      @select="onPaymentTypeConfirm"
      @close="showPaymentType = false"
    />
        <!-- æ—¥æœŸé€‰æ‹©å™¨ -->
        <u-popup v-model="showDate" mode="bottom">
            <u-datetime-picker
                v-model="currentDate"
                title="选择日期"
                @confirm="onDateConfirm"
                @cancel="showDate = false"
            />
        </u-popup>
        <up-datetime-picker
            :show="showDate"
            v-model="form.paymentDate"
            @confirm="onDateConfirm"
            @cancel="showDate = false"
            mode="date"
        />
    </view>
</template>
@@ -77,9 +98,11 @@
}
import { ref, onMounted, computed } from 'vue'
import FooterButtons from '@/components/FooterButtons.vue'
import useUserStore from '@/store/modules/user'
import { useDict } from '@/utils/dict'
import {paymentRegistrationAdd, paymentRegistrationEdit} from "@/api/procurementManagement/paymentEntry";
import { formatDateToYMD } from '@/utils/ruoyi'
const userStore = useUserStore()
@@ -89,9 +112,7 @@
// å“åº”式数据
const loading = ref(false)
const showPaymentType = ref(false)
const pickerValue = ref([])
const showDate = ref(false)
const currentDate = ref([new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()])
// è¡¨å•数据
const form = ref({
@@ -110,7 +131,6 @@
  ticketRegistrationId: ''
})
const currentNoReceiptAmount = ref(0)
const operationType = ref('')
// èŽ·å–å­—å…¸æ•°æ®
const { receipt_payment_type: dictReceiptPaymentType } = useDict('receipt_payment_type')
@@ -118,7 +138,7 @@
// è½¬æ¢å­—典数据格式为选择器需要的格式
const receipt_payment_type = computed(() => {
  return dictReceiptPaymentType.value.map(item => ({
    text: item.label,
    name: item.label,
    value: item.value
  }))
})
@@ -141,10 +161,9 @@
}
// ç¡®è®¤ä»˜æ¬¾æ–¹å¼é€‰æ‹©
const onPaymentTypeConfirm = ({ selectedValues, selectedOptions }) => {
  form.value.receiptPaymentType = selectedOptions[0].value
  form.value.paymentMethod = selectedOptions[0].text
    pickerValue.value = selectedValues;
const onPaymentTypeConfirm = (item) => {
  form.value.receiptPaymentType = item.value
  form.value.paymentMethod = item.name
  showPaymentType.value = false
}
@@ -154,9 +173,8 @@
}
// ç¡®è®¤æ—¥æœŸé€‰æ‹©
const onDateConfirm = ({ selectedValues }) => {
  form.value.paymentDate = selectedValues.join('-')
    currentDate.value = selectedValues
const onDateConfirm = (e) => {
  form.value.paymentDate = formatDateToYMD(e.value)
  showDate.value = false
}
@@ -169,6 +187,10 @@
  }
  if (!form.value.receiptPaymentType) {
    showNotify({ type: 'warning', message: '请选择付款形式' })
    return
  }
  if (!form.value.paymentDate) {
    showNotify({ type: 'warning', message: '请选择付款日期' })
    return
  }
  loading.value = true
@@ -187,14 +209,7 @@
    const rowStr = uni.getStorageSync('invoiceLedgerEditRow')
    const row = JSON.parse(rowStr)
    form.value = { ...row };
    form.value.ticketRegistrationId = row.id;
    form.value.id = "";
    if (operationType.value === 'add') {
        currentNoReceiptAmount.value = row.unPaymentAmountTotal
        form.value.registrant = userStore.nickName
        form.value.registrationtDate = getCurrentDate();
        form.value.paymentDate = getCurrentDate();
    }
    currentNoReceiptAmount.value = row.unPaymentAmountTotal || 0;
}
// èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
function getCurrentDate() {
@@ -205,57 +220,10 @@
    return `${year}-${month}-${day}`;
}
onMounted(() => {
    operationType.value = uni.getStorageSync('operationType') || '';
  initData()
})
</script>
<style scoped lang="scss">
.account-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; }
@import '@/static/scss/form-common.scss';
</style>
src/pages/procurementManagement/paymentEntry/index.vue
@@ -22,7 +22,7 @@
            <!-- ç­›é€‰å¼€å…³ -->
            <view class="switch-row">
                <text class="switch-label">不显示待付款为0</text>
                <van-switch v-model="searchForm.status" @change="getList" size="18"/>
                <u-switch v-model="searchForm.status" @change="getList" active-color="#2979ff" inactive-color="#e5e5e5"/>
            </view>
        </view>
@@ -70,7 +70,7 @@
                    
                    <!-- æ“ä½œæŒ‰é’® -->
                    <view class="action-buttons">
                        <van-button
                        <u-button
                            type="primary"
                            size="small"
                            class="action-btn"
@@ -78,7 +78,7 @@
                            @click="openForm('add', item)"
                        >
                            æ–°å¢žä»˜æ¬¾
                        </van-button>
                        </u-button>
                    </view>
                </view>
            </view>
@@ -94,7 +94,13 @@
<script setup>
import { ref } from 'vue'
import useUserStore from '@/store/modules/user'
import { showToast } from 'vant'
// æ›¿æ¢ toast æ–¹æ³•
const showToast = (message) => {
  uni.showToast({
    title: message,
    icon: 'none'
  })
}
import {onShow} from "@dcloudio/uni-app";
import {invoiceListPage} from "@/api/procurementManagement/procurementInvoiceLedger";
src/pages/procurementManagement/paymentLedger/detail.vue
@@ -1,7 +1,7 @@
<template>
    <view class="receipt-payment-detail">
        <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
        <PageHeader title="客户往来详情" @back="goBack" />
        <PageHeader title="供应商往来详情" @back="goBack" />
        
        <!-- ç»Ÿè®¡ä¿¡æ¯ -->
        <view class="summary-info" v-if="tableData.length > 0">
@@ -114,9 +114,11 @@
    }
    const param = {
        supplierId: supplierId.value,
        current: -1,
        size: -1
    };
    paymentLedgerList(param).then((res) => {
        tableData.value = res.data;
        tableData.value = res.data.records;
    }).catch(() => {
        uni.showToast({
            title: '查询失败',
src/pages/procurementManagement/procurementInvoiceLedger/detail.vue
@@ -3,53 +3,62 @@
        <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
        <PageHeader title="编辑来票台账" @back="goBack" />
        <van-form @submit="submitForm" ref="formRef" label-width="120px" 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.purchaseContractNumber" label="采购合同号" readonly />
                <van-field v-model="form.salesContractNo" label="销售合同号" readonly />
                <van-field v-model="form.taxInclusiveUnitPrice" label="含税单价(元)" readonly />
                <van-field v-model="form.createdAt" label="创建时间" readonly />
                <van-field v-model="form.invoiceNumber" label="发票号" placeholder="请输入" readonly />
                <van-field v-model="form.ticketsNum" label="来票数" type="number" placeholder="请输入" required :rules="[{ required: true, message: '请输入来票数' }]" @change="inputTicketsNum"/>
                <van-field v-model="form.ticketsAmount" label="本次来票金额(元)" type="number" placeholder="请输入" required :rules="[{ required: true, message: '请输入本次来票金额' }]" @change="inputTicketsAmount"/>
                <view class="tip-text">未来票数:{{ formatAmount(form.futureTickets) }} å…ƒ</view>
<!--                <van-field v-model="form.invoicePerson" label="未来票数" readonly />-->
            </van-cell-group>
        <up-form @submit="submitForm" ref="formRef" label-width="120" :model="form">
            <up-form-item label="采购合同号" prop="purchaseContractNumber">
                <up-input v-model="form.purchaseContractNumber" placeholder="自动生成" disabled />
            </up-form-item>
            <up-form-item label="销售合同号" prop="salesContractNo">
                <up-input v-model="form.salesContractNo" placeholder="自动生成" disabled />
            </up-form-item>
            <up-form-item label="含税单价(元)" prop="taxInclusiveUnitPrice">
                <up-input v-model="form.taxInclusiveUnitPrice" placeholder="自动生成" disabled />
            </up-form-item>
            <up-form-item label="创建时间" prop="createdAt">
                <up-input v-model="form.createdAt" placeholder="自动生成" disabled />
            </up-form-item>
            <up-form-item label="发票号" prop="invoiceNumber">
                <up-input v-model="form.invoiceNumber" placeholder="请输入" disabled />
            </up-form-item>
            <up-form-item label="来票数" prop="ticketsNum" required :rules="rules.ticketsNum">
                <up-input
                    v-model="form.ticketsNum"
                    type="number"
                    placeholder="请输入"
                    @blur="inputTicketsNum"
                />
            </up-form-item>
            <up-form-item label="本次来票金额(元)" prop="ticketsAmount" required :rules="rules.ticketsAmount">
                <up-input
                    v-model="form.ticketsAmount"
                    type="number"
                    placeholder="请输入"
                    @blur="inputTicketsAmount"
                />
            </up-form-item>
            <view class="tip-text">未来票数:{{ formatAmount(form.futureTickets) }} å…ƒ</view>
<!--            <van-cell-group title="附件材料(仅支持 pdf)" inset>-->
<!--                <van-uploader-->
<!--                    accept=".pdf"-->
<!--                    multiple-->
<!--                    :after-read="afterReadUpload"-->
<!--                    :before-read="beforeReadPdf"-->
<!--                >-->
<!--                    <van-button class="upload-btn" icon="plus" type="primary" block>上传文件</van-button>-->
<!--                </van-uploader>-->
<!--                <view class="uploaded-list" v-if="fileList.length">-->
<!--                    <view class="uploaded-item" v-for="(f, idx) in fileList" :key="idx">-->
<!--                        <text class="file-name">{{ f.name || getFileNameFromUrl(f.url) }}</text>-->
<!--                        <van-button size="mini" type="danger" plain @click="removeUploaded(idx)">移除</van-button>-->
<!--                    </view>-->
<!--                </view>-->
<!--            </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">保存</van-button>
            </view>
        </van-form>
            <!-- ä½¿ç”¨å…¬å…±åº•部按钮组件 -->
            <FooterButtons
                show
                cancelText="取消"
                confirmText="保存"
                @cancel="goBack"
                @confirm="onSubmit"
            />
        </up-form>
    </view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { showToast, showLoadingToast, closeToast } from 'vant'
import dayjs from 'dayjs'
import useUserStore from '@/store/modules/user'
import { getToken } from '@/utils/auth'
import { invoiceLedgerSaveOrUpdate } from '@/api/salesManagement/invoiceLedger.js'
import config from '@/config.js'
import {getProductRecordById, updateRegistration} from "@/api/procurementManagement/procurementInvoiceLedger";
import PageHeader from '@/components/PageHeader.vue';
import FooterButtons from '@/components/FooterButtons.vue';
const userStore = useUserStore()
@@ -72,21 +81,34 @@
const currentId = ref('')
const temFutureTickets = ref(0)
// æ—¥æœŸé€‰æ‹©
const currentInvoiceDate = ref([new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()])
// è¡¨å•校验规则
const rules = {
    ticketsNum: [
        { required: true, message: '请输入来票数', trigger: 'blur' }
    ],
    ticketsAmount: [
        { required: true, message: '请输入本次来票金额', trigger: 'blur' }
    ]
};
const goBack = () => {
    uni.removeStorageSync('invoiceLedgerEditRow');
    uni.navigateBack()
}
const inputTicketsNum = (val) => {
const inputTicketsNum = () => {
    // ç¡®ä¿å«ç¨Žå•价存在且不为零
    if (!form.value.taxInclusiveUnitPrice || Number(form.value.taxInclusiveUnitPrice) === 0) {
        showToast("含税单价不能为零或未定义");
        uni.showToast({
            title: "含税单价不能为零或未定义",
            icon: 'none'
        });
        return;
    }
    if (Number(form.value.ticketsNum) > Number(temFutureTickets.value)) {
        showToast("来票数不得大于未来票数");
        uni.showToast({
            title: "来票数不得大于未来票数",
            icon: 'none'
        });
        form.value.ticketsNum = temFutureTickets.value
    }
    
@@ -96,15 +118,21 @@
    form.value.futureTickets = Number(futureTickets.toFixed(2));
    form.value.ticketsAmount = Number(ticketsAmount.toFixed(2));
};
const inputTicketsAmount = (val) => {
const inputTicketsAmount = () => {
    // ç¡®ä¿å«ç¨Žå•价存在且不为零
    if (!form.value.taxInclusiveUnitPrice || Number(form.value.taxInclusiveUnitPrice) === 0) {
        showToast("含税单价不能为零或未定义");
        uni.showToast({
            title: "含税单价不能为零或未定义",
            icon: 'none'
        });
        return;
    }
    
    if (Number(val) > Number(form.value.futureTickets*form.value.taxInclusiveUnitPrice)) {
        showToast("本次来票金额不得大于总金额");
    if (Number(form.value.ticketsAmount) > Number(form.value.futureTickets*form.value.taxInclusiveUnitPrice)) {
        uni.showToast({
            title: "本次来票金额不得大于总金额",
            icon: 'none'
        });
        form.value.ticketsAmount = (form.value.futureTickets*form.value.taxInclusiveUnitPrice).toFixed(2)
        const ticketsNum = Number(form.value.ticketsAmount) / Number(form.value.taxInclusiveUnitPrice);
        form.value.ticketsNum = Number(ticketsNum.toFixed(2))
@@ -112,7 +140,7 @@
    }
    
    // ç¡®ä¿æ‰€æœ‰æ•°å€¼éƒ½è½¬æ¢ä¸ºæ•°å­—类型进行计算
    const ticketsNum = Number(val) / Number(form.value.taxInclusiveUnitPrice);
    const ticketsNum = Number(form.value.ticketsAmount) / Number(form.value.taxInclusiveUnitPrice);
    form.value.ticketsNum = Number(ticketsNum.toFixed(2));
};
const formatAmount = (val) => {
@@ -122,114 +150,11 @@
    return num.toFixed(2)
}
// ä¸Šä¼ å‰æ ¡éªŒï¼ˆå…¼å®¹ Vant Uploader çš„ file/fileList ç»“构)
const beforeReadPdf = (file) => {
    const items = Array.isArray(file) ? file : [file]
    for (const it of items) {
        const raw = it?.file || it
        const fileName = raw?.name || it?.name || ''
        const ext = fileName.split('.').pop()?.toLowerCase()
        const sizeOk = (raw?.size || 0) <= 10 * 1024 * 1024
        if (ext !== 'pdf') {
            showToast('仅支持pdf文件')
            return false
        }
        if (!sizeOk) {
            showToast('上传文件大小不能超过10MB')
            return false
        }
    }
    return true
}
const uploadSingleFile = async (fileObj) => {
    return new Promise((resolve, reject) => {
        showLoadingToast({ message: '正在上传...' })
        const baseUrl = config.baseUrl + '/invoiceLedger/uploadFile'
        const filePath = fileObj?.url || fileObj?.tempFilePath || fileObj?.file?.path
        if (filePath) {
            uni.uploadFile({
                url: baseUrl,
                filePath,
                name: 'file',
                header: { Authorization: 'Bearer ' + getToken() },
                success: (res) => {
                    closeToast()
                    try {
                        const data = JSON.parse(res.data || '{}')
                        if (data.code === 200) {
                            resolve(data.data)
                        } else {
                            reject(new Error(data.msg || '上传失败'))
                        }
                    } catch (err) {
                        reject(err)
                    }
                },
                fail: (err) => {
                    closeToast()
                    reject(err)
                }
            })
            return
        }
        // H5: ä½¿ç”¨åŽŸå§‹ File(input é€‰æ‹©ï¼‰
        const rawFile = fileObj?.file
        if (rawFile) {
            // uni.uploadFile åœ¨ H5 ä¸æ”¯æŒåŽŸç”Ÿ File å¯¹è±¡ï¼Œè¿™é‡Œç”¨ fetch å‘送 FormData
            const formData = new FormData()
            formData.append('file', rawFile, rawFile.name || 'file.pdf')
            formData.append('salesLedgerId', form.value.salesLedgerId || currentId.value || '')
            fetch(baseUrl, {
                method: 'POST',
                headers: { Authorization: 'Bearer ' + getToken() },
                body: formData
            }).then(async (res) => {
                closeToast()
                const data = await res.json()
                if (data.code === 200) {
                    resolve(data.data)
                } else {
                    reject(new Error(data.msg || '上传失败'))
                }
            }).catch((err) => {
                closeToast()
                reject(err)
            })
            return
        }
        closeToast()
        reject(new Error('未找到可上传的文件'))
    })
}
const afterReadUpload = async (file) => {
    try {
        const files = Array.isArray(file) ? file : file?.file ? [file] : [file]
        for (const f of files) {
            const uploaded = await uploadSingleFile(f)
            fileList.value.push(uploaded)
        }
        showToast('上传成功')
    } catch (e) {
        showToast('上传失败')
    }
}
const removeUploaded = (index) => {
    fileList.value.splice(index, 1)
}
const getFileNameFromUrl = (url) => {
    try { if (!url) return ''; return decodeURIComponent(url.split('/').pop()) } catch (e) { return url }
}
const loadDetail = async (id) => {
    try {
        showLoadingToast({ message: '加载中...' })
        uni.showLoading({
            title: '加载中...'
        });
        const res = await getProductRecordById({ id })
        const data = res?.data || res
        form.value = { ...data }
@@ -241,25 +166,43 @@
        if (!form.value.invoiceDate) {
            form.value.invoiceDate = dayjs().format('YYYY-MM-DD')
        }
        closeToast()
        uni.hideLoading();
    } catch (e) {
        closeToast()
        showToast('加载失败')
        uni.hideLoading();
        uni.showToast({
            title: '加载失败',
            icon: 'none'
        });
    }
}
const submitForm = async () => {
    try {
        showLoadingToast({ message: '提交中...' })
        // æäº¤è¡¨å•的具体逻辑
        await updateRegistration(form.value)
        closeToast()
        showToast('提交成功')
        uni.showToast({
            title: '提交成功',
            icon: 'success'
        });
        setTimeout(() => { goBack() }, 800)
    } catch (e) {
        closeToast()
        showToast('提交失败,请重试')
        uni.showToast({
            title: '提交失败,请重试',
            icon: 'none'
        });
    }
}
// è¡¨å•提交
const onSubmit = () => {
    formRef.value.validate().then(() => {
        // è¡¨å•验证通过,提交表单
        submitForm();
    }).catch(error => {
        // è¡¨å•验证失败
        console.log('表单验证失败', error);
    });
};
onMounted(() => {
    const rowStr = uni.getStorageSync('invoiceLedgerEditRow')
@@ -272,21 +215,10 @@
            // ignore
        }
    }
})
});
</script>
<style scoped lang="scss">
.account-detail {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 5rem;
}
.uploaded-list { padding: 8px 16px 0 16px; }
.uploaded-item { display: flex; align-items: center; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #f5f5f5; }
.file-name { font-size: 12px; color: #333; margin-right: 8px; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tip-text { padding: 4px 16px 0 16px; font-size: 12px; color: #888; }
.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; }
@import '@/static/scss/form-common.scss';
</style>
src/pages/procurementManagement/procurementInvoiceLedger/index.vue
@@ -3,7 +3,7 @@
        <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
        <PageHeader title="来票台账" @back="goBack" />
        
        <!-- æœç´¢å’Œç­›é€‰åŒºåŸŸï¼ˆä¿æŒä¸Žé”€å”®å°è´¦é£Žæ ¼ä¸€è‡´ï¼‰ -->
        <!-- æœç´¢åŒºåŸŸ -->
        <view class="search-filter-section">
            <view class="search-bar">
                <view class="search-input">
@@ -15,9 +15,6 @@
                        @confirm="handleQuery"
                    />
                </view>
                <!--                <view class="filter-button" @click="showFilter = true">-->
                <!--                    <up-icon name="list" size="24" color="#999"></up-icon>-->
                <!--                </view>-->
                <view class="filter-button" @click="handleQuery">
                    <up-icon name="search" size="24" color="#999"></up-icon>
                </view>
@@ -90,10 +87,9 @@
                            <text class="detail-label">录入日期</text>
                            <text class="detail-value">{{ item.createdAt }}</text>
                        </view>
                    </view>
                    <view class="action-buttons">
                        <van-button
                        <u-button
                            type="primary"
                            size="small"
                            class="action-btn"
@@ -101,9 +97,9 @@
                            @click="openEdit(item)"
                        >
                            ç¼–辑
                        </van-button>
                        <van-button
                            type="danger"
                        </u-button>
                        <u-button
                            type="error"
                            size="small"
                            plain
                            class="action-btn"
@@ -111,8 +107,8 @@
                            @click="handleDelete(item)"
                        >
                            åˆ é™¤
                        </van-button>
                        <van-button
                        </u-button>
                        <u-button
                            type="default"
                            size="small"
                            plain
@@ -121,8 +117,8 @@
                            @click="openFileActions(item.commonFiles || [])"
                        >
                            æŸ¥çœ‹é™„ä»¶
                        </van-button>
                        <van-button
                        </u-button>
                        <u-button
                            type="primary"
                            size="small"
                            class="action-btn"
@@ -131,7 +127,7 @@
                            @click="openUpload(item)"
                        >
                            ä¸Šä¼ 
                        </van-button>
                        </u-button>
                    </view>
                </view>
            </view>
@@ -140,84 +136,40 @@
            <text>暂无来票台账数据</text>
        </view>
        
        <!-- ç­›é€‰å¼¹çª— -->
        <van-popup v-model:show="showFilter" position="bottom" round>
            <view class="filter-popup">
                <van-cell-group title="筛选条件" inset>
                    <van-field
                        label="来票日期"
                        readonly
                        @click="showInvoiceRange = true"
                        :placeholder="invoiceRangeLabel || '请选择日期范围'"
                    />
                    <van-field
                        label="录入日期"
                        readonly
                        @click="showCreateDatePicker = true"
                        :placeholder="searchForm.createTimeStart || '请选择录入日期'"
                    />
                    <view class="switch-row">
                        <text class="switch-label">不显示有发票行</text>
                        <van-switch v-model="searchForm.status" size="20" />
                    </view>
                </van-cell-group>
                <view class="filter-actions">
                    <van-button @click="resetFilter">重置</van-button>
                    <van-button type="primary" @click="confirmFilter">确定</van-button>
                </view>
            </view>
        </van-popup>
        <!-- æ—¥åŽ†ï¼šæ¥ç¥¨æ—¥æœŸèŒƒå›´ -->
        <van-popup v-model:show="showInvoiceRange" position="bottom">
            <van-calendar
                title="选择来票日期范围"
                type="range"
                color="#2979ff"
                @confirm="onInvoiceRangeConfirm"
                @cancel="showInvoiceRange = false"
            />
        </van-popup>
        <!-- æ—¥æœŸï¼šå½•入日期 -->
        <van-popup v-model:show="showCreateDatePicker" position="bottom">
            <van-date-picker
                v-model="currentCreateDate"
                title="选择录入日期"
                @confirm="onCreateDateConfirm"
                @cancel="showCreateDatePicker = false"
            />
        </van-popup>
        <!-- å•行上传弹窗(无表单) -->
        <van-popup v-model:show="showUpload" position="bottom" round>
        <u-popup v-model="showUpload" mode="bottom" border-radius="10">
            <view class="upload-container">
                <van-cell-group title="上传附件(仅支持 pdf,最大10MB,最多10个)" inset>
                    <van-uploader
                        accept="*"
                        multiple
                        :max-count="10"
                        :after-read="afterReadRowUpload"
                        :before-read="beforeReadPdf"
                    />
                    <view class="uploaded-list" v-if="fileList.length">
                        <view class="uploaded-item" v-for="(f, idx) in fileList" :key="idx">
                            <text class="file-name">{{ f.name || getFileNameFromUrl(f.url) }}</text>
                            <van-button size="mini" type="danger" plain @click="removeUploaded(idx)">移除</van-button>
                        </view>
                <view class="popup-header">
                    <text class="popup-title">上传附件(仅支持 pdf,最大10MB,最多10个)</text>
                </view>
                <u-upload
                    ref="uploadRef"
                    accept="file"
                    multiple
                    :max-count="10"
                    :show-progress="true"
                    :before-upload="beforeReadPdf"
                    :action="uploadUrl"
                    :header="{ Authorization: 'Bearer ' + getToken() }"
                    name="file"
                    @on-success="onUploadSuccess"
                    @on-error="onUploadError"
                />
                <view class="uploaded-list" v-if="fileList.length">
                    <view class="uploaded-item" v-for="(f, idx) in fileList" :key="idx">
                        <text class="file-name">{{ f.name || getFileNameFromUrl(f.url) }}</text>
                        <u-button size="mini" type="error" plain @click="removeUploaded(idx)">移除</u-button>
                    </view>
                </van-cell-group>
                </view>
                <view class="filter-actions">
                    <van-button @click="showUpload = false">取消</van-button>
                    <van-button type="primary" @click="confirmUpload">确认</van-button>
                    <u-button @click="showUpload = false" type="default" size="default" style="width: 150px;">取消</u-button>
                    <u-button @click="confirmUpload" type="primary" size="default" style="width: 150px;">确认</u-button>
                </view>
            </view>
        </van-popup>
        </u-popup>
        
        <!-- é™„件列表选择 -->
        <van-action-sheet v-model:show="showFileSheet" :actions="fileActions" cancel-text="取消" close-on-click-action @select="onSelectFile" />
        <u-action-sheet v-model="showFileSheet" :list="fileActions" :cancel-btn="true" @click="onSelectFile" @close="showFileSheet = false" />
    </view>
</template>
@@ -241,6 +193,8 @@
import {onShow} from "@dcloudio/uni-app";
import {productRecordPage} from "@/api/procurementManagement/procurementInvoiceLedger";
import {delRegistration} from "@/api/procurementManagement/invoiceEntry";
import PageHeader from '@/components/PageHeader.vue';
import FooterButtons from '@/components/FooterButtons.vue';
const userStore = useUserStore()
@@ -250,19 +204,12 @@
const page = reactive({ current: -1, size: -1 })
const searchForm = reactive({
    searchText: '',
    status: false,
    createTimeStart: ''
})
// é¡¶éƒ¨äº¤äº’
const showFilter = ref(false)
const showInvoiceRange = ref(false)
const showCreateDatePicker = ref(false)
const invoiceRangeLabel = ref('')
const currentCreateDate = ref([new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()])
const currentId = ref('')
const fileList = ref([]) // è¡Œä¸Šä¼ æˆ–通用上传列表
const uploadRef = ref()
const uploadUrl = config.baseUrl + '/invoiceLedger/uploadFile'
// è¡Œä¸Šä¼ å¼¹çª—
const showUpload = ref(false)
@@ -293,65 +240,19 @@
const getList = async () => {
    try {
        showLoadingToast({ message: '加载中...' })
        const { invoiceDate, ...rest } = searchForm
        const res = await productRecordPage({ ...rest, ...page })
        uni.showLoading({
            title: '加载中...'
        });
        const res = await productRecordPage({ ...searchForm, ...page })
        // å…¼å®¹ä¸åŒè¿”回结构
        const records = res?.data?.records || res?.records || res?.data || []
        const totalVal = res?.data?.total || res?.total || records.length || 0
        ledgerList.value = records
        total.value = totalVal
        closeToast()
        uni.hideLoading();
    } catch (e) {
        closeToast()
        uni.hideLoading();
        showToast('获取列表失败')
    }
}
// ç­›é€‰é€»è¾‘
const resetFilter = () => {
    searchForm.searchText = ''
    searchForm.status = false
    const start = dayjs().startOf('month').format('YYYY-MM-DD')
    const end = dayjs().endOf('month').format('YYYY-MM-DD')
    searchForm.invoiceDate = [start, end]
    searchForm.invoiceDateStart = start
    searchForm.invoiceDateEnd = end
    searchForm.createTimeStart = ''
    invoiceRangeLabel.value = ''
}
const confirmFilter = () => {
    showFilter.value = false
    getList()
}
const onInvoiceRangeConfirm = (e) => {
    // e ä¸º [start, end] çš„ Date å¯¹è±¡æˆ–字符串,uni-app ä¸‹ Vant Calendar è¿”回时间戳数组
    try {
        let start, end
        if (Array.isArray(e)) {
            const [s, ed] = e
            start = dayjs(s).format('YYYY-MM-DD')
            end = dayjs(ed).format('YYYY-MM-DD')
        } else if (e && e.detail && Array.isArray(e.detail)) {
            const [s, ed] = e.detail
            start = dayjs(s).format('YYYY-MM-DD')
            end = dayjs(ed).format('YYYY-MM-DD')
        }
        searchForm.invoiceDateStart = start
        searchForm.invoiceDateEnd = end
        invoiceRangeLabel.value = `${start} è‡³ ${end}`
        showInvoiceRange.value = false
    } catch (err) {
        showInvoiceRange.value = false
    }
}
const onCreateDateConfirm = ({ selectedValues }) => {
    try {
        searchForm.createTimeStart = selectedValues.join('-')
        currentCreateDate.value = selectedValues
        showCreateDatePicker.value = false
    } catch (err) {
        showCreateDatePicker.value = false
    }
}
@@ -376,13 +277,15 @@
        success: async (res) => {
            if (res.confirm) {
                try {
                    showLoadingToast({ message: '处理中...' })
                    uni.showLoading({
                        title: '处理中...'
                    });
                    await delRegistration(ids)
                    closeToast()
                    uni.hideLoading();
                    showToast('删除成功')
                    await getList()
                } catch (e) {
                    closeToast()
                    uni.hideLoading();
                    showToast('删除失败,请重试')
                }
            }
@@ -399,16 +302,18 @@
const confirmUpload = async () => {
    try {
        const payload = { fileList: fileList.value, id: currentId.value }
        showLoadingToast({ message: '提交中...' })
        uni.showLoading({
            title: '提交中...'
        });
        await commitFile(payload)
        closeToast()
        uni.hideLoading();
        showToast('提交成功')
        showUpload.value = false
        fileList.value = []
        currentId.value = ''
        getList()
    } catch (e) {
        closeToast()
        uni.hideLoading();
        showToast('提交失败,请重试')
    }
}
@@ -432,59 +337,24 @@
    return true
}
const uploadSingleFile = async (fileObj) => {
    return new Promise((resolve, reject) => {
        showLoadingToast({ message: '正在上传...' })
        uni.uploadFile({
            url: config.baseUrl + '/invoiceLedger/uploadFile',
            filePath: fileObj.url || fileObj.file?.path || fileObj.tempFilePath,
            name: 'file',
            header: { Authorization: 'Bearer ' + getToken() },
            success: (res) => {
                closeToast()
                try {
                    const data = JSON.parse(res.data || '{}')
                    if (data.code === 200) {
                        resolve(data.data)
                    } else {
                        reject(new Error(data.msg || '上传失败'))
                    }
                } catch (err) {
                    reject(err)
                }
            },
            fail: (err) => {
                closeToast()
                reject(err)
            }
        })
    })
}
const afterReadEditUpload = async (file) => {
// uview-plus çš„上传成功回调
const onUploadSuccess = (res, file) => {
    try {
        const files = Array.isArray(file) ? file : file?.file ? [file] : [file]
        for (const f of files) {
            const uploaded = await uploadSingleFile(f)
            fileList.value.push(uploaded)
        const data = JSON.parse(res.data || '{}')
        if (data.code === 200) {
            fileList.value.push(data.data)
            showToast('上传成功')
        } else {
            showToast('上传失败: ' + (data.msg || '未知错误'))
        }
        showToast('上传成功')
    } catch (e) {
    } catch (err) {
        showToast('上传失败')
    }
}
const afterReadRowUpload = async (file) => {
    try {
        const files = Array.isArray(file) ? file : file?.file ? [file] : [file]
        for (const f of files) {
            const uploaded = await uploadSingleFile(f)
            fileList.value.push(uploaded)
        }
        showToast('上传成功')
    } catch (e) {
        showToast('上传失败')
    }
// uview-plus çš„上传失败回调
const onUploadError = (err) => {
    showToast('上传失败')
}
const removeUploaded = (index) => {
@@ -503,18 +373,23 @@
// é™„件查看
const openFileActions = (commonFiles) => {
    currentFilesToOpen = commonFiles || []
    fileActions.value = (commonFiles || []).map((f, idx) => ({ name: getFileNameFromUrl(f.url || ''), index: idx }))
    fileActions.value = (commonFiles || []).map((f, idx) => ({
        title: getFileNameFromUrl(f.url || ''),
        index: idx
    }))
    showFileSheet.value = true
}
const onSelectFile = async (action) => {
    try {
        const item = currentFilesToOpen[action.index]
        if (!item || !item.url) return
        showLoadingToast({ message: '下载中...' })
        uni.showLoading({
            title: '下载中...'
        });
        uni.downloadFile({
            url: item.url,
            success: (res) => {
                closeToast()
                uni.hideLoading();
                if (res.statusCode === 200) {
                    uni.openDocument({ filePath: res.tempFilePath })
                } else {
@@ -522,12 +397,12 @@
                }
            },
            fail: () => {
                closeToast()
                uni.hideLoading();
                showToast('下载失败')
            }
        })
    } catch (e) {
        closeToast()
        uni.hideLoading();
        showToast('打开失败')
    }
}
@@ -687,27 +562,15 @@
    padding: 12px 12px 20px;
}
.switch-row {
    padding: 12px 16px;
    display: flex;
    align-items: center;
    justify-content: space-between;
.popup-header {
    padding: 10px 16px;
    border-bottom: 1px solid #f5f5f5;
}
.switch-label {
    font-size: 14px;
.popup-title {
    font-size: 16px;
    font-weight: 500;
    color: #333;
}
.filter-actions {
    display: flex;
    gap: 12px;
    padding: 12px 16px 16px;
    justify-content: space-between;
}
.edit-container {
    padding-bottom: 5rem;
}
.uploaded-list {
@@ -738,6 +601,13 @@
    color: #888;
}
.filter-actions {
    display: flex;
    gap: 12px;
    padding: 12px 16px 16px;
    justify-content: center;
}
.footer-btns {
    position: fixed;
    left: 0;
src/pages/procurementManagement/procurementLedger/detail.vue
@@ -4,258 +4,345 @@
        <PageHeader title="台账详情" @back="goBack" />
         <!-- è¡¨å•区域 -->
        <van-form @submit="onSubmit" label-width="110px" input-align="right" style="margin-top: 10px" error-message-align="right" scroll-to-error scroll-to-error-position="center">
            <van-field label="采购合同号" name="purchaseContractNumber" borderBottom="true" v-model="form.purchaseContractNumber" placeholder="自动生成" :rules="[{ required: true, message: '请输入' }]">
            </van-field>
            <van-field
                v-model="form.salesContractNo"
                is-link
                readonly
                name="salesContractNo"
                label="销售合同号"
                required
                placeholder="点击选择销售合同号"
                :rules="[{ required: true, message: '请选择销售合同号' }]"
                @click="showPicker = true"
            />
            <van-field
                v-model="form.supplierName"
                is-link
                readonly
                required
                name="supplierName"
                label="供应商名称"
                placeholder="点击选择供应商"
                :rules="[{ required: true, message: '请选择供应商' }]"
                @click="showCustomerPicker = true"
            />
            <van-field label="项目名称" name="projectName" borderBottom="true" v-model="form.projectName" placeholder="请输入项目名称" :rules="[{ required: true, message: '项目名称不能为空' }]" required>
            </van-field>
            <van-field label="付款方式" name="paymentMethod" borderBottom="true" v-model="form.paymentMethod" placeholder="请输入付款方式">
            </van-field>
            <van-field label="录入人" name="recorderName" borderBottom="true" v-model="form.recorderName" placeholder="请输入" disabled>
            </van-field>
            <van-field label="录入日期" name="entryDate" borderBottom="true" v-model="form.entryDate" placeholder="请输入" disabled>
            </van-field>
            <van-popup v-model:show="showPicker" destroy-on-close position="bottom">
                <van-picker
                    :columns="salesContractList"
                    v-model="pickerValue"
                    @confirm="onConfirm"
                    @cancel="showPicker = false"
        <up-form @submit="onSubmit" label-width="110" ref="formRef" :rules="rules" :model="form">
                <up-form-item label="采购合同号" prop="purchaseContractNumber">
                    <up-input v-model="form.purchaseContractNumber" placeholder="自动生成" disabled />
                </up-form-item>
                <up-form-item
                    label="销售合同号"
                    prop="salesContractNo"
                    required
                    @click="showPicker = true"
                >
                    <up-input
                        v-model="form.salesContractNo"
                        readonly=""
                        @click="showPicker = true"
                        placeholder="点击选择销售合同号"
                    />
                    <template #right>
                        <up-icon
                            name="arrow-right"
                            @click="showPicker = true"
                        ></up-icon>
                    </template>
                </up-form-item>
                <up-form-item
                    label="供应商名称"
                    prop="supplierName"
                    required
                    @click="showCustomerPicker = true"
                >
                    <up-input
                        v-model="form.supplierName"
                        readonly=""
                        @click="showCustomerPicker = true"
                        placeholder="点击选择供应商"
                    />
                    <template #right>
                        <up-icon
                            name="arrow-right"
                            @click="showCustomerPicker = true"
                        ></up-icon>
                    </template>
                </up-form-item>
                <up-form-item label="项目名称" prop="projectName" required >
                    <up-input
                        v-model="form.projectName"
                        placeholder="请输入项目名称"
                    />
                </up-form-item>
                <up-form-item label="付款方式" prop="paymentMethod" >
                    <up-input v-model="form.paymentMethod" placeholder="请输入付款方式" />
                </up-form-item>
                <up-form-item label="录入人" prop="recorderName" >
                    <up-input v-model="form.recorderName" placeholder="请输入" disabled />
                </up-form-item>
                <up-form-item label="录入日期" prop="entryDate" >
                    <up-input v-model="form.entryDate" placeholder="请输入" disabled />
                </up-form-item>
                <!-- é”€å”®åˆåŒå·é€‰æ‹© -->
                <up-action-sheet
                    :show="showPicker"
                    :actions="salesContractActionList"
                    title="选择销售合同号"
                    @select="onSalesmanSelect"
                    @close="showPicker = false"
                />
            </van-popup>
            <van-popup v-model:show="showCustomerPicker" destroy-on-close position="bottom">
                <van-picker
                    :columns="supplierList"
                    v-model="pickerCustomerValue"
                    @confirm="onCustomerConfirm"
                    @cancel="showCustomerPicker = false"
                <!-- ä¾›åº”商选择 -->
                <up-action-sheet
                    :show="showCustomerPicker"
                    :actions="supplierActionList"
                    title="选择供应商"
                    @select="onCustomerSelect"
                    @close="showCustomerPicker = false"
                />
            </van-popup>
            <!-- äº§å“å¤§ç±»é€‰æ‹©å™¨ -->
            <van-popup v-model:show="showCategoryPicker" destroy-on-close position="bottom">
                <!-- å¤´éƒ¨æŒ‰é’®åŒºåŸŸ -->
                <view class="popup-header">
                    <view @click="showCategoryPicker = false" class="cancelButton">取消</view>
                    <view @click="confirmCategorySelection" class="confirmButton">确定</view>
                </view>
                <up-tree
                    :data="productOptions"
                    :props="defaultProps"
                    show-checkbox
                    default-expand-all
                    check-strictly
                    @check-change="onCategoryConfirm"
                <!-- äº§å“å¤§ç±»é€‰æ‹©å™¨ -->
                <up-popup :show="showCategoryPicker" mode="bottom">
                    <!-- å¤´éƒ¨æŒ‰é’®åŒºåŸŸ -->
                    <view class="popup-header">
                        <view @click="showCategoryPicker = false" class="cancelButton">取消</view>
                        <view @click="confirmCategorySelection" class="confirmButton">确定</view>
                    </view>
                    <u-tree
                        :data="productOptions"
                        :props="defaultProps"
                        show-checkbox
                        default-expand-all
                        check-strictly
                        @check-change="onCategoryConfirm"
                    />
                </up-popup>
                <!-- è§„格型号选择器 -->
                <up-action-sheet
                    :show="showSpecificationPicker"
                    :actions="specificationActionList"
                    title="选择规格型号"
                    @select="onSpecificationSelect"
                    @close="showSpecificationPicker = false"
                />
            </van-popup>
            <!-- è§„格型号选择器 -->
            <van-popup v-model:show="showSpecificationPicker" destroy-on-close position="bottom">
                <van-picker
                    :columns="modelOptions"
                    v-model="pickerSpecificationValue"
                    @confirm="onSpecificationConfirm"
                    @cancel="showSpecificationPicker = false"
                <!-- ç¨ŽçŽ‡é€‰æ‹©å™¨ -->
                <up-action-sheet
                    :show="showTaxRatePicker"
                    :actions="taxRateActionList"
                    title="选择税率"
                    @select="onTaxRateSelect"
                    @close="showTaxRatePicker = false"
                />
            </van-popup>
            <!-- ç¨ŽçŽ‡é€‰æ‹©å™¨ -->
            <van-popup v-model:show="showTaxRatePicker" destroy-on-close position="bottom">
                <van-picker
                    :columns="taxRateOptions"
                    v-model="pickerTaxRateValue"
                    @confirm="onTaxRateConfirm"
                    @cancel="showTaxRatePicker = false"
                <!-- å‘票类型选择器 -->
                <up-action-sheet
                    :show="showInvoiceTypePicker"
                    :actions="invoiceTypeActionList"
                    title="选择发票类型"
                    @select="onInvoiceTypeSelect"
                    @close="showInvoiceTypePicker = false"
                />
            </van-popup>
            <!-- å‘票类型选择器 -->
            <van-popup v-model:show="showInvoiceTypePicker" destroy-on-close position="bottom">
                <van-picker
                    :columns="invoiceTypeOptions"
                    v-model="pickerInvoiceTypeValue"
                    @confirm="onInvoiceTypeConfirm"
                    @cancel="showInvoiceTypePicker = false"
                />
            </van-popup>
            <!-- äº§å“ä¿¡æ¯ -->
            <view class="product-section">
                <view class="section-header">
                    <text class="section-title">产品信息</text>
                    <van-button type="primary" size="small" @click="addProduct" class="add-btn" icon="plus"  v-if="operationType !== 'view'">新增</van-button>
                </view>
                <view class="product-card" v-for="(product, idx) in productData" :key="idx">
                    <!-- äº§å“ç±» -->
                    <view class="product-header">
                        <view class="product-title">
                            <van-icon name="description" color="#2979ff" size="15" />
                            <text class="product-productCategory">产品 {{ idx + 1 }}</text>
                <!-- äº§å“ä¿¡æ¯ -->
                <view class="product-section">
                    <view class="section-header">
                        <view>
                        <text class="section-title">产品信息</text>
                        </view>
                        <!-- æ“ä½œæŒ‰é’® -->
                        <view class="product-actions"  v-if="operationType !== 'view'">
                            <van-button type="danger" size="mini" @click="removeProduct(idx)" class="del-btn" icon="delete">删除</van-button>
                        <view>
                            <up-button type="primary" size="small" @click="addProduct" class="add-btn" v-if="operationType !== 'view'">
                            æ–°å¢ž
                        </up-button>
                        </view>
                    </view>
                    <!-- äº§å“ä¿¡æ¯è¡¨å• -->
                    <view class="product-form">
                        <!-- äº§å“å¤§ç±» -->
                        <van-field
                            v-model="product.productCategory"
                            is-link
                            readonly
                            name="productCategory"
                            label="产品大类"
                            required
                            placeholder="请选择"
                            :rules="[{ required: true, message: '请选择' }]"
                            @click="openCategoryPicker(idx)"
                        />
                    <view class="product-card" v-for="(product, idx) in productData" :key="idx">
                        <!-- äº§å“ç±» -->
                        <view class="product-header">
                            <view class="product-title">
                                <up-icon name="file-text" size="16" color="#2979ff"></up-icon>
                                <text class="product-productCategory">产品 {{ idx + 1 }}</text>
                            </view>
                            <!-- æ“ä½œæŒ‰é’® -->
                            <view class="product-actions" v-if="operationType !== 'view'">
                                <up-button type="error" size="mini" @click="removeProduct(idx)" class="del-btn">
                                    åˆ é™¤
                                </up-button>
                            </view>
                        </view>
                        
                        <!-- è§„格型号 -->
                        <van-field
                            v-model="product.specificationModel"
                            is-link
                            readonly
                            name="specificationModel"
                            label="规格型号"
                            required
                            :rules="[{ required: true, message: '请选择' }]"
                            placeholder="请选择"
                            @click="openSpecificationPicker(idx)"
                        />
                        <!-- å•位 -->
                        <van-field
                            v-model="product.unit"
                            name="unit"
                            label="单位"
                            required
                            :rules="[{ required: true, message: '请输入' }]"
                            placeholder="请输入"
                        />
                        <!-- ç¨Žçއ -->
                        <van-field
                            v-model="product.taxRate"
                            is-link
                            readonly
                            name="taxRate"
                            label="税率(%)"
                            required
                            :rules="[{ required: true, message: '请选择' }]"
                            placeholder="请选择"
                            @click="openTaxRatePicker(idx)"
                        />
                        <!-- å«ç¨Žå•ä»· -->
                        <van-field
                            v-model="product.taxInclusiveUnitPrice"
                            name="taxInclusiveUnitPrice"
                            label="含税单价(元)"
                            type="number"
                            required
                            :rules="[{ required: true, message: '请输入' }]"
                            placeholder="请输入"
                            @blur="formatTaxPrice(idx)"
                        />
                        <!-- æ•°é‡ -->
                        <van-field
                            v-model="product.quantity"
                            name="quantity"
                            label="数量"
                            type="number"
                            :rules="[{ required: true, message: '请输入' }]"
                            required
                            placeholder="请输入"
                            @blur="formatAmount(idx)"
                        />
                        <!-- å«ç¨Žæ€»ä»· -->
                        <van-field
                            v-model="product.taxInclusiveTotalPrice"
                            name="taxInclusiveTotalPrice"
                            label="含税总价(元)"
                            type="number"
                            :rules="[{ required: true, message: '请输入' }]"
                            required
                            placeholder="请输入"
                            @blur="formatTaxTotal(idx)"
                        />
                        <!-- ä¸å«ç¨Žæ€»ä»· -->
                        <van-field
                            v-model="product.taxExclusiveTotalPrice"
                            name="taxExclusiveTotalPrice"
                            label="不含税总价(元)"
                            type="number"
                            required
                            :rules="[{ required: true, message: '请输入' }]"
                            placeholder="请输入"
                            @blur="formatNoTaxTotal(idx)"
                        />
                        <!-- å‘票类型 -->
                        <van-field
                            v-model="product.invoiceType"
                            is-link
                            readonly
                            name="invoiceType"
                            label="发票类型"
                            :rules="[{ required: true, message: '请选择' }]"
                            required
                            placeholder="请选择"
                            @click="openInvoiceTypePicker(idx)"
                        />
                        <!-- äº§å“ä¿¡æ¯è¡¨å• -->
                        <view class="product-form">
                            <!-- äº§å“å¤§ç±» -->
                            <up-form-item
                                label="产品大类"
                                prop="productCategory"
                                required
                                :rules="productRules"
                            >
                                <up-input
                                    v-model="product.productCategory"
                                    readonly
                                    placeholder="请选择"
                                    @click="openCategoryPicker(idx)"
                                />
                                <template #right>
                                    <up-icon
                                        name="arrow-right"
                                        @click="showCategoryPicker = true"
                                    ></up-icon>
                                </template>
                            </up-form-item>
                            <!-- è§„格型号 -->
                            <up-form-item
                                label="规格型号"
                                prop="specificationModel"
                                required
                                :rules="productRules"
                            >
                                <up-input
                                    v-model="product.specificationModel"
                                    readonly
                                    placeholder="请选择"
                                    @click="openSpecificationPicker(idx)"
                                />
                                <template #right>
                                    <up-icon
                                        name="arrow-right"
                                        @click="showSpecificationPicker = true"
                                    ></up-icon>
                                </template>
                            </up-form-item>
                            <!-- å•位 -->
                            <up-form-item
                                label="单位"
                                prop="unit"
                                required
                                :rules="productRules"
                            >
                                <up-input
                                    v-model="product.unit"
                                    placeholder="请输入"
                                />
                            </up-form-item>
                            <!-- ç¨Žçއ -->
                            <up-form-item
                                label="税率(%)"
                                prop="taxRate"
                                required
                                :rules="productRules"
                            >
                                <up-input
                                    v-model="product.taxRate"
                                    readonly
                                    placeholder="请选择"
                                    @click="openTaxRatePicker(idx)"
                                />
                                <template #right>
                                    <up-icon
                                        name="arrow-right"
                                        @click="showTaxRatePicker = true"
                                    ></up-icon>
                                </template>
                            </up-form-item>
                            <!-- å«ç¨Žå•ä»· -->
                            <up-form-item
                                label="含税单价(元)"
                                prop="taxInclusiveUnitPrice"
                                required
                                :rules="productRules"
                            >
                                <up-input
                                    v-model="product.taxInclusiveUnitPrice"
                                    type="number"
                                    placeholder="请输入"
                                    @blur="formatTaxPrice(idx)"
                                />
                            </up-form-item>
                            <!-- æ•°é‡ -->
                            <up-form-item
                                label="数量"
                                prop="quantity"
                                required
                                :rules="productRules"
                            >
                                <up-input
                                    v-model="product.quantity"
                                    type="number"
                                    placeholder="请输入"
                                    @blur="formatAmount(idx)"
                                />
                            </up-form-item>
                            <!-- å«ç¨Žæ€»ä»· -->
                            <up-form-item
                                label="含税总价(元)"
                                prop="taxInclusiveTotalPrice"
                                required
                                :rules="productRules"
                            >
                                <up-input
                                    v-model="product.taxInclusiveTotalPrice"
                                    type="number"
                                    placeholder="请输入"
                                    @blur="formatTaxTotal(idx)"
                                />
                            </up-form-item>
                            <!-- ä¸å«ç¨Žæ€»ä»· -->
                            <up-form-item
                                label="不含税总价(元)"
                                prop="taxExclusiveTotalPrice"
                                required
                                :rules="productRules"
                            >
                                <up-input
                                    v-model="product.taxExclusiveTotalPrice"
                                    type="number"
                                    placeholder="请输入"
                                    @blur="formatNoTaxTotal(idx)"
                                />
                            </up-form-item>
                            <!-- å‘票类型 -->
                            <up-form-item
                                label="发票类型"
                                prop="invoiceType"
                                required
                                :rules="productRules"
                            >
                                <up-input
                                    v-model="product.invoiceType"
                                    readonly
                                    placeholder="请选择"
                                    @click="openInvoiceTypePicker(idx)"
                                />
                                <template #right>
                                    <up-icon
                                        name="arrow-right"
                                        @click="showInvoiceTypePicker = true"
                                    ></up-icon>
                                </template>
                            </up-form-item>
                        </view>
                    </view>
                </view>
            </view>
            <view class="footer-btns" v-if="operationType !== 'view'">
                <van-button class="cancel-btn" @click="goBack">取消</van-button>
                <van-button class="save-btn" native-type="submit" form-type="submit">保存</van-button>
            </view>
        </van-form>
                <!-- ä½¿ç”¨å…¬å…±åº•部按钮组件 -->
                <FooterButtons
                    :show="operationType !== 'view'"
                    cancelText="取消"
                    confirmText="保存"
                    @cancel="goBack"
                    @confirm="onSubmit"
                />
        </up-form>
  </view>
</template>
<script setup>
import {onMounted, ref} from 'vue';
import {onMounted, ref, computed} from 'vue';
import { modelList, productTreeList } from "@/api/basicData/product.js";
import useUserStore from "@/store/modules/user";
import {calculateTaxExclusiveTotalPrice} from "@/utils/summarizeTable";
import {formatDateToYMD} from '@/utils/ruoyi'
import {
    addOrEditPurchase, createPurchaseNo,
    getOptions,
    getPurchaseById,
    getSalesNo
} from "@/api/procurementManagement/procurementLedger";
import PageHeader from '@/components/PageHeader.vue';
import FooterButtons from '@/components/FooterButtons.vue';
// èŽ·å–é¡µé¢å‚æ•°
const operationType = ref('');
const editData = ref(null);
const formRef = ref(null);
const userStore = useUserStore()
const form = ref({
@@ -270,23 +357,33 @@
    recorderName: '',
    entryDate: '',
});
const pickerValue = ref(['']);
const pickerDateValue = ref([]);
const showPicker = ref(false);
const pickerCustomerValue = ref(['']);
const showCustomerPicker = ref(false);
const salesContractList = ref([]);
const supplierList = ref([]);
const productData = ref([]);
// è®¡ç®—销售合同号选择列表
const salesContractActionList = computed(() => {
    return salesContractList.value.map(item => ({
        name: item.text,
        value: item.value
    }))
})
// è®¡ç®—供应商选择列表
const supplierActionList = computed(() => {
    return supplierList.value.map(item => ({
        name: item.text,
        value: item.value
    }))
})
// é€‰æ‹©å™¨ç›¸å…³å˜é‡
const showCategoryPicker = ref(false);
const showSpecificationPicker = ref(false);
const showTaxRatePicker = ref(false);
const showInvoiceTypePicker = ref(false);
const pickerSpecificationValue = ref(['']);
const pickerTaxRateValue = ref(['']);
const pickerInvoiceTypeValue = ref(['']);
const currentProductIndex = ref(0);
// é€‰é¡¹æ•°æ®
@@ -299,7 +396,6 @@
});
const modelOptions = ref([]);
// é˜²æ­¢å¾ªçŽ¯è®¡ç®—çš„æ ‡å¿—
const taxRateOptions = ref([
  { text: '1', value: '1' },
  { text: '6', value: '6' },
@@ -310,6 +406,79 @@
  { text: '增普票', value: '增普票' },
  { text: '增专票', value: '增专票' },
]);
// è®¡ç®—规格型号选择列表
const specificationActionList = computed(() => {
    return modelOptions.value.map(model => ({
        name: model.text,
        value: model.value,
        unit: model.unit
    }))
})
// è®¡ç®—税率选择列表
const taxRateActionList = computed(() => {
    return taxRateOptions.value.map(rate => ({
        name: rate.text,
        value: rate.value
    }))
})
// è®¡ç®—发票类型选择列表
const invoiceTypeActionList = computed(() => {
    return invoiceTypeOptions.value.map(type => ({
        name: type.text,
        value: type.value
    }))
})
// è¡¨å•校验规则
const rules = {
    salesContractNo: [
        { required: true, message: '请选择销售合同号', trigger: 'blur' }
    ],
    supplierName: [
        { required: true, message: '请选择供应商名称', trigger: 'blur' }
    ],
    projectName: [
        { required: true, message: '请输入项目名称', trigger: 'blur' }
    ]
};
// äº§å“ä¿¡æ¯æ ¡éªŒè§„则
const productRules = {
    productCategory: [
        { required: true, message: '请选择产品大类', trigger: 'blur' }
    ],
    specificationModel: [
        { required: true, message: '请选择规格型号', trigger: 'blur' }
    ],
    unit: [
        { required: true, message: '请输入单位', trigger: 'blur' }
    ],
    taxRate: [
        { required: true, message: '请选择税率', trigger: 'blur' }
    ],
    taxInclusiveUnitPrice: [
        { required: true, message: '请输入含税单价', trigger: 'blur' },
        { type: 'number', min: 0, message: '含税单价必须大于0', trigger: 'blur' }
    ],
    quantity: [
        { required: true, message: '请输入数量', trigger: 'blur' },
        { type: 'number', min: 0, message: '数量必须大于0', trigger: 'blur' }
    ],
    taxInclusiveTotalPrice: [
        { required: true, message: '请输入含税总价', trigger: 'blur' },
        { type: 'number', min: 0, message: '含税总价必须大于0', trigger: 'blur' }
    ],
    taxExclusiveTotalPrice: [
        { required: true, message: '请输入不含税总价', trigger: 'blur' },
        { type: 'number', min: 0, message: '不含税总价必须大于0', trigger: 'blur' }
    ],
    invoiceType: [
        { required: true, message: '请选择发票类型', trigger: 'blur' }
    ]
};
const addProduct = () => {
    if (productData.value === null) {
@@ -328,18 +497,29 @@
    invoiceType: ''
  });
};
const onConfirm = ({ selectedValues, selectedOptions }) => {
    form.value.salesContractNo = selectedOptions[0]?.text;
    form.value.salesLedgerId = selectedOptions[0]?.value;
    pickerValue.value = [selectedValues[0]];
// é”€å”®åˆåŒå·é€‰æ‹©äº‹ä»¶
const onSalesmanSelect = (item) => {
    form.value.salesContractNo = item.name
    // æŸ¥æ‰¾å¯¹åº”çš„id
    const selectedItem = salesContractList.value.find(contract => contract.text === item.name);
    if (selectedItem) {
        form.value.salesLedgerId = selectedItem.value;
    }
    showPicker.value = false;
};
const onCustomerConfirm = ({ selectedValues, selectedOptions }) => {
    form.value.supplierName = selectedOptions[0]?.text;
    form.value.supplierId = selectedOptions[0]?.value;
    pickerCustomerValue.value = [selectedValues[0]];
}
// ä¾›åº”商选择事件
const onCustomerSelect = (item) => {
    form.value.supplierName = item.name
    // æŸ¥æ‰¾å¯¹åº”çš„id
    const selectedItem = supplierList.value.find(supplier => supplier.text === item.name);
    if (selectedItem) {
        form.value.supplierId = selectedItem.value;
    }
    showCustomerPicker.value = false;
};
}
const removeProduct = (idx) => {
    productData.value.splice(idx, 1);
};
@@ -383,11 +563,11 @@
        selectedCategoryNode.value = null;
        productData.value[currentProductIndex.value].specificationModel = ''
        productData.value[currentProductIndex.value].productModelId = ''
        productData.value[currentProductIndex.value].pickerSpecificationValue = ['']
        getModels(id)
    }
    showCategoryPicker.value = false;
};
// èŽ·å–è§„æ ¼åž‹å·
const getModels = (value) => {
    modelList({ id: value }).then((res) => {
@@ -398,39 +578,32 @@
        }));
    });
};
// é€‰æ‹©è§„格型号
const onSpecificationConfirm = ({ selectedValues, selectedOptions }) => {
    productData.value[currentProductIndex.value].specificationModel = selectedOptions[0]?.text;
    productData.value[currentProductIndex.value].productModelId = selectedOptions[0]?.value;
    productData.value[currentProductIndex.value].unit = selectedOptions[0]?.unit;
  pickerSpecificationValue.value = [selectedValues[0]];
  showSpecificationPicker.value = false;
};
// é€‰æ‹©ç¨Žçއ
const onTaxRateConfirm = ({ selectedValues, selectedOptions }) => {
    productData.value[currentProductIndex.value].taxRate = selectedOptions[0]?.value;
  pickerTaxRateValue.value = [selectedValues[0]];
  showTaxRatePicker.value = false;
    // if (isCalculating.value) return;
    const inclusiveTotalPrice = parseFloat(productData.value[currentProductIndex.value].taxInclusiveTotalPrice);
    const taxRate = parseFloat(productData.value[currentProductIndex.value].taxRate);
    if (!inclusiveTotalPrice || !taxRate) {
        return;
    }
    // isCalculating.value = true;
    // è®¡ç®—不含税总价
    productData.value[currentProductIndex.value].taxExclusiveTotalPrice =
        calculateTaxExclusiveTotalPrice(
            inclusiveTotalPrice,
            taxRate
        );
    // isCalculating.value = false;
// è§„格型号选择事件
const onSpecificationSelect = (item) => {
    productData.value[currentProductIndex.value].specificationModel = item.name
    productData.value[currentProductIndex.value].productModelId = item.value
    productData.value[currentProductIndex.value].unit = item.unit
    showSpecificationPicker.value = false;
};
const onInvoiceTypeConfirm = ({ selectedValues, selectedOptions }) => {
    productData.value[currentProductIndex.value].invoiceType = selectedOptions[0]?.text;
  pickerInvoiceTypeValue.value = [selectedValues[0]];
  showInvoiceTypePicker.value = false;
// ç¨ŽçŽ‡é€‰æ‹©äº‹ä»¶
const onTaxRateSelect = (item) => {
    productData.value[currentProductIndex.value].taxRate = item.value
    showTaxRatePicker.value = false;
    // é‡æ–°è®¡ç®—不含税总价
    const inclusiveTotalPrice = parseFloat(productData.value[currentProductIndex.value].taxInclusiveTotalPrice)
    const taxRate = parseFloat(item.value)
    if (inclusiveTotalPrice && taxRate) {
        productData.value[currentProductIndex.value].taxExclusiveTotalPrice =
            calculateTaxExclusiveTotalPrice(inclusiveTotalPrice, taxRate)
    }
};
// å‘票类型选择事件
const onInvoiceTypeSelect = (item) => {
    productData.value[currentProductIndex.value].invoiceType = item.name
    showInvoiceTypePicker.value = false;
};
// æ ¼å¼åŒ–函数 - å›ºå®šä¸¤ä½å°æ•°
@@ -459,13 +632,14 @@
    
    // å¦‚果有税率,计算不含税总价
    if (productData.value[currentProductIndex.value].taxRate) {
        productData.value[currentProductIndex.value].taxExclusiveTotalPrice =
        productData.value[currentProductIndex.value].taxExclusiveTotalPrice =
            calculateTaxExclusiveTotalPrice(
                productData.value[currentProductIndex.value].taxInclusiveTotalPrice,
                productData.value[currentProductIndex.value].taxRate
            );
    }
};
// æ•°é‡è¾“入框失焦
const formatAmount = (idx) => {
  if (productData.value[idx].quantity) {
@@ -491,13 +665,14 @@
    productData.value[currentProductIndex.value].taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2);
    // å¦‚果有税率,计算不含税总价
    if (productData.value[currentProductIndex.value].taxRate) {
        productData.value[currentProductIndex.value].taxExclusiveTotalPrice =
        productData.value[currentProductIndex.value].taxExclusiveTotalPrice =
            calculateTaxExclusiveTotalPrice(
                productData.value[currentProductIndex.value].taxInclusiveTotalPrice,
                productData.value[currentProductIndex.value].taxRate
            );
    }
};
// å«ç¨Žæ€»ä»·å¤±ç„¦ï¼Œæ ¹æ®å«ç¨Žæ€»ä»·è®¡ç®—含税单价和数量
const formatTaxTotal = (idx) => {
  if (productData.value[idx].taxInclusiveTotalPrice) {
@@ -516,13 +691,14 @@
    productData.value[currentProductIndex.value].taxInclusiveUnitPrice = (totalPrice / quantity).toFixed(2);
    // å¦‚果有税率,计算不含税总价
    if (productData.value[currentProductIndex.value].taxRate) {
        productData.value[currentProductIndex.value].taxExclusiveTotalPrice =
        productData.value[currentProductIndex.value].taxExclusiveTotalPrice =
            calculateTaxExclusiveTotalPrice(
                totalPrice,
                productData.value[currentProductIndex.value].taxRate
            );
    }
};
// ä¸å«ç¨Žæ€»ä»·å¤±ç„¦, æ ¹æ®ä¸å«ç¨Žæ€»ä»·è®¡ç®—含税单价和数量
const formatNoTaxTotal = (idx) => {
  if (productData.value[idx].taxExclusiveTotalPrice) {
@@ -551,12 +727,14 @@
    // è®¡ç®—含税单价 = å«ç¨Žæ€»ä»· / æ•°é‡
    productData.value[currentProductIndex.value].taxInclusiveUnitPrice = (inclusiveTotalPrice / quantity).toFixed(2);
};
const goBack = () => {
    // æ¸…理本地存储的数据
    uni.removeStorageSync('operationType');
    uni.removeStorageSync('editData');
    uni.navigateBack();
};
const onSubmit = () => {
    if (productData.value !== null && productData.value.length > 0) {
        form.value.productData = JSON.parse(JSON.stringify(productData.value));
@@ -576,6 +754,7 @@
        goBack();
    });
};
const setUserInfo = () => {
    form.value.recorderId = userStore.id;
    form.value.recorderName = userStore.nickName;
@@ -585,8 +764,8 @@
    const month = String(today.getMonth() + 1).padStart(2, '0')
    const day = String(today.getDate()).padStart(2, '0')
    form.value.entryDate = `${year}-${month}-${day}`
    pickerDateValue.value = [year.toString(), month.toString(), day.toString()]
}
};
// å¡«å……表单数据(编辑模式)
const fillFormData = () => {
  if (!editData.value) return;
@@ -598,7 +777,6 @@
  form.value.salesContractNo = editData.value.salesContractNo || '';
  form.value.supplierName = editData.value.supplierName || '';
  form.value.projectName = editData.value.projectName || '';
  form.value.executionDate = editData.value.executionDate || '';
  form.value.paymentMethod = editData.value.paymentMethod || '';
  form.value.salesLedgerId = editData.value.salesLedgerId || '';
  form.value.recorderId = editData.value.recorderId || '';
@@ -606,29 +784,8 @@
  form.value.entryDate = editData.value.entryDate || '';
  form.value.id = editData.value.id || '';
  form.value.supplierId = editData.value.supplierId || '';
  // è®¾ç½®é”€å”®åˆåŒå·é€‰æ‹©å™¨çš„值
  if (editData.value.salesContractNo) {
    const salesmanIndex = salesContractList.value.findIndex(user => user.text === editData.value.salesContractNo);
    if (salesmanIndex !== -1) {
      pickerValue.value = [salesContractList.value[salesmanIndex].value];
    }
  }
  // è®¾ç½®ä¾›åº”商选择器的值
  if (editData.value.supplierName) {
    const customerIndex = supplierList.value.findIndex(customer => customer.text === editData.value.supplierName);
    if (customerIndex !== -1) {
      pickerCustomerValue.value = [supplierList.value[customerIndex].value]
    }
  }
  // è®¾ç½®æ—¥æœŸé€‰æ‹©å™¨çš„值
  if (editData.value.executionDate) {
        pickerDateValue.value = editData.value.executionDate.split('-').map(num => parseInt(num, 10))
        console.log(pickerDateValue.value)
    }
};
const getSalesNoList = () => {
    getSalesNo().then((res) => {
        // å°†ç”¨æˆ·æ•°æ®ç»„装成 picker éœ€è¦çš„æ ¼å¼
@@ -637,7 +794,8 @@
            value: user.id
        }));
    })
}
};
const getOptionsLIst = () => {
    getOptions().then((res) => {
        // å°†ç”¨æˆ·æ•°æ®ç»„装成 picker éœ€è¦çš„æ ¼å¼
@@ -646,7 +804,8 @@
            value: item.id
        }));
    })
}
};
const convertIdToValue = (data) => {
    // å¦‚果传入的不是数组,则返回空数组
    if (!Array.isArray(data)) {
@@ -666,12 +825,14 @@
        return mappedItem;
    });
};
// èŽ·å–äº§å“å¤§ç±»tree数据
const getProductOptions = () => {
    productTreeList().then((res) => {
        productOptions.value = convertIdToValue(res);
    });
};
onMounted(() => {
    // èŽ·å–é¡µé¢å‚æ•°
    operationType.value = uni.getStorageSync('operationType') || '';
@@ -710,153 +871,5 @@
</script>
<style scoped lang="scss">
.account-detail {
  min-height: 100vh;
  background: #f8f9fa;
  padding-bottom: 5rem;
}
.header {
  display: flex;
  align-items: center;
  background: #fff;
  padding: 1rem 1.25rem;
  border-bottom: 0.0625rem solid #f0f0f0;
  position: sticky;
  top: 0;
  z-index: 100;
    /* å…¼å®¹ iOS åˆ˜æµ·/灵动岛安全区 */
    padding-top: env(safe-area-inset-top);
}
.title {
  flex: 1;
  text-align: center;
  font-size: 1.125rem;
  font-weight: 600;
  color: #333;
}
.form-section {
    margin-top: 1rem;
}
.van-field {
    height: 3.4rem;
}
.van-cell {
    align-items: center;
}
.product-section {
  background: #fff;
    margin-top: 1rem;
  padding: 1rem;
  box-shadow: 0 0.125rem 0.5rem rgba(0,0,0,0.04);
}
.section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1rem;
}
.section-title {
  font-size: 1rem;
  font-weight: 600;
  color: #333;
}
.product-card {
    background: #FFFFFF;
    box-shadow: 0 0 1.25rem 0 rgba(0,57,117,0.08);
    border-radius: 0.5rem 0.5rem 0.5rem 0.5rem;
  padding: 1rem 0.5rem 0 0.5rem;
  position: relative;
}
.product-header {
  display: flex;
  align-items: center;
    justify-content: space-between;
  padding: 0 0.5rem 0.75rem 0.5rem;
  border-bottom: 0.0625rem solid #e8e8e8;
}
.product-productCategory {
  margin-left: 0.5rem;
  font-size: 0.875rem;
  font-weight: 500;
  color: #333;
}
.info-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.75rem;
  margin-bottom: 1rem;
}
.info-item {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.info-label {
  font-size: 0.75rem;
  color: #666;
  font-weight: 400;
}
.info-value {
  font-size: 0.875rem;
  color: #333;
  font-weight: 500;
}
.info-value.highlight {
  color: #2979ff;
  font-weight: 600;
}
.product-form {
  margin-bottom: 1rem;
}
.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;
}
.popup-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 1rem;
    background: #fff;
    position: sticky;
    top: 0;
    z-index: 10;
}
.cancelButton {
    color: #969799
}
.confirmButton {
    color: #1989FA
}
.u-tree {
    height: 13rem;
}
@import '@/static/scss/form-common.scss';
</style>
src/pages/procurementManagement/receiptPaymentHistory/index.vue
@@ -72,7 +72,7 @@
                        </view>
                        <!-- æ“ä½œæŒ‰é’® -->
                        <view class="action-buttons">
                            <van-button
                            <u-button
                                type="primary"
                                size="small"
                                class="action-btn"
@@ -80,7 +80,7 @@
                                @click="openForm(item)"
                            >
                                ç¼–辑付款
                            </van-button>
                            </u-button>
                        </view>
                    </view>
                </view>
src/pages/sales/invoiceLedger/detail.vue
@@ -1,5 +1,5 @@
<template>
    <view class="invoice-detail">
    <view class="account-view">
        <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
        <PageHeader title="发票详情" @back="goBack" />
        
@@ -34,7 +34,6 @@
                    :beforeRead="beforeReadPdf"
                >
                    <u-button class="upload-btn" type="primary">
                        <u-icon name="plus" size="14" />
                        ä¸Šä¼ æ–‡ä»¶
                    </u-button>
                </u-upload>
@@ -87,6 +86,7 @@
import { ref, onMounted } from 'vue'
import dayjs from 'dayjs'
import { formatDateToYMD } from '@/utils/ruoyi'
import useUserStore from '@/store/modules/user'
import { getToken } from '@/utils/auth'
import { invoiceLedgerProductInfo, invoiceLedgerSaveOrUpdate } from '@/api/salesManagement/invoiceLedger.js'
@@ -127,10 +127,11 @@
    return num.toFixed(2)
}
const onInvoiceDateConfirm = ({ selectedValues }) => {
    form.value.invoiceDate = selectedValues.join('-')
    currentInvoiceDate.value = selectedValues
    showInvoiceDatePicker.value = false
// æ—¥æœŸç¡®è®¤äº‹ä»¶
const onInvoiceDateConfirm = (e) => {
    form.value.invoiceDate = formatDateToYMD(e.value)
    currentInvoiceDate.value = formatDateToYMD(e.value)
    showInvoiceDatePicker.value = false;
}
// ä¸Šä¼ å‰æ ¡éªŒï¼ˆå…¼å®¹ Vant Uploader çš„ file/fileList ç»“构)
@@ -155,7 +156,7 @@
const uploadSingleFile = async (fileObj) => {
    return new Promise((resolve, reject) => {
        showLoadingToast({ message: '正在上传...' })
        showLoadingToast('正在上传...')
        const baseUrl = config.baseUrl + '/invoiceLedger/uploadFile'
        const filePath = fileObj?.url || fileObj?.tempFilePath || fileObj?.file?.path
@@ -240,7 +241,7 @@
const loadDetail = async (id) => {
    try {
        showLoadingToast({ message: '加载中...' })
        showLoadingToast('加载中...')
        const res = await invoiceLedgerProductInfo({ id })
        const data = res?.data || res
        form.value = { ...data }
@@ -263,7 +264,7 @@
        if (!form.value.invoiceNo) { showToast('请输入发票号'); return }
        if (!form.value.invoiceTotal) { showToast('请输入发票金额'); return }
        if (!form.value.invoiceDate) { showToast('请选择开票日期'); return }
        showLoadingToast({ message: '提交中...' })
        showLoadingToast('提交中...')
        form.value.fileList = fileList.value
        await invoiceLedgerSaveOrUpdate(form.value)
        closeToast()
@@ -290,17 +291,88 @@
</script>
<style scoped lang="scss">
.account-detail {
@import '@/static/scss/form-common.scss';
.account-view {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 5rem;
}
.info-section {
    background: #fff;
    margin-bottom: 16px;
}
.info-grid {
    display: flex;
    flex-wrap: wrap;
    padding: 16px;
}
.info-item {
    display: flex;
    width: 100%;
    margin-bottom: 12px;
    &:last-child {
        margin-bottom: 0;
    }
    .info-label {
        font-size: 14px;
        color: #999;
        min-width: 100px;
    }
    .info-value {
        font-size: 14px;
        color: #333;
        flex: 1;
    }
    .info-value.highlight {
        color: #2979ff;
        font-weight: 500;
    }
    .info-value.medium {
        font-size: 16px;
    }
}
.uploaded-list { padding: 8px 16px 0 16px; }
.uploaded-item { display: flex; align-items: center; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #f5f5f5; }
.file-name { font-size: 12px; color: #333; margin-right: 8px; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tip-text { padding: 4px 16px 0 16px; font-size: 12px; color: #888; }
.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; }
.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;
}
</style>
src/pages/sales/invoiceLedger/index.vue
@@ -84,7 +84,7 @@
                        </view>
                    </view>
                    <view class="action-buttons">
                        <van-button
                        <up-button
                            type="primary"
                            size="small"
                            class="action-btn"
@@ -92,9 +92,9 @@
                            @click="openEdit(item)"
                        >
                            ç¼–辑
                        </van-button>
                        <van-button
                            type="danger"
                        </up-button>
                        <up-button
                            type="error"
                            size="small"
                            plain
                            class="action-btn"
@@ -102,9 +102,8 @@
                            @click="handleDelete(item)"
                        >
                            åˆ é™¤
                        </van-button>
                        <van-button
                            type="default"
                        </up-button>
                        <up-button
                            size="small"
                            plain
                            class="action-btn"
@@ -112,8 +111,8 @@
                            @click="openFileActions(item.commonFiles || [])"
                        >
                            æŸ¥çœ‹é™„ä»¶
                        </van-button>
                        <van-button
                        </up-button>
                        <up-button
                            type="primary"
                            size="small"
                            class="action-btn"
@@ -122,7 +121,7 @@
                            @click="openUpload(item)"
                        >
                            ä¸Šä¼ 
                        </van-button>
                        </up-button>
                    </view>
                </view>
            </view>
@@ -132,94 +131,113 @@
        </view>
        <!-- ç­›é€‰å¼¹çª— -->
        <van-popup v-model:show="showFilter" position="bottom" round>
        <up-popup v-model="showFilter" mode="bottom" round><up-transition>
            <view class="filter-popup">
                <van-cell-group title="筛选条件" inset>
                    <van-field
                <up-cell-group title="筛选条件" inset>
                    <up-input
                        label="开票日期"
                        readonly
                        placeholder="请选择日期范围"
                        @click="showInvoiceRange = true"
                        :placeholder="invoiceRangeLabel || '请选择日期范围'"
                        v-model="invoiceRangeLabel"
                    />
                    <van-field
                    <up-input
                        label="录入日期"
                        readonly
                        @click="showCreateDatePicker = true"
                        :placeholder="searchForm.createTimeStart || '请选择录入日期'"
                        v-model="searchForm.createTimeStart"
                    />
                    <view class="switch-row">
                        <text class="switch-label">不显示有发票行</text>
                        <van-switch v-model="searchForm.status" size="20" />
                        <up-switch v-model="searchForm.status" size="20" />
                    </view>
                </van-cell-group>
                </up-cell-group>
                <view class="filter-actions">
                    <van-button @click="resetFilter">重置</van-button>
                    <van-button type="primary" @click="confirmFilter">确定</van-button>
                    <up-button @click="resetFilter">重置</up-button>
                    <up-button type="primary" @click="confirmFilter">确定</up-button>
                </view>
            </view>
        </van-popup>
        </up-transition></up-popup>
        <!-- æ—¥åŽ†ï¼šå¼€ç¥¨æ—¥æœŸèŒƒå›´ -->
        <van-popup v-model:show="showInvoiceRange" position="bottom">
            <van-calendar
                title="选择开票日期范围"
        <up-popup v-model="showInvoiceRange" mode="bottom"><up-transition>
            <up-datetime-picker
                mode="date"
                type="range"
                color="#2979ff"
                title="选择开票日期范围"
                @confirm="onInvoiceRangeConfirm"
                @cancel="showInvoiceRange = false"
            />
        </van-popup>
        </up-transition></up-popup>
        <!-- æ—¥æœŸï¼šå½•入日期 -->
        <van-popup v-model:show="showCreateDatePicker" position="bottom">
            <van-date-picker
        <up-popup v-model="showCreateDatePicker" mode="bottom"><up-transition>
            <up-datetime-picker
                mode="date"
                type="selector"
                v-model="currentCreateDate"
                title="选择录入日期"
                @confirm="onCreateDateConfirm"
                @cancel="showCreateDatePicker = false"
            />
        </van-popup>
        </up-transition></up-popup>
        
        <!-- å•行上传弹窗(无表单) -->
        <van-popup v-model:show="showUpload" position="bottom" round>
        <up-popup v-model="showUpload" mode="bottom" round><up-transition>
            <view class="upload-container">
                <van-cell-group title="上传附件(仅支持 pdf,最大10MB,最多10个)" inset>
                    <van-uploader
                        accept="*"
                <up-cell-group title="上传附件(仅支持 pdf,最大10MB,最多10个)" inset>
                    <up-upload
                        accept="pdf"
                        multiple
                        :max-count="10"
                        :after-read="afterReadRowUpload"
                        :before-read="beforeReadPdf"
                    />
                        :maxCount="10"
                        :afterRead="afterReadRowUpload"
                        :beforeRead="beforeReadPdf"
                    >
                        <up-button type="primary">点击上传</up-button>
                    </up-upload>
                    <view class="uploaded-list" v-if="fileList.length">
                        <view class="uploaded-item" v-for="(f, idx) in fileList" :key="idx">
                            <text class="file-name">{{ f.name || getFileNameFromUrl(f.url) }}</text>
                            <van-button size="mini" type="danger" plain @click="removeUploaded(idx)">移除</van-button>
                            <up-button size="mini" type="error" plain @click="removeUploaded(idx)">移除</up-button>
                        </view>
                    </view>
                </van-cell-group>
                </up-cell-group>
                <view class="filter-actions">
                    <van-button @click="showUpload = false">取消</van-button>
                    <van-button type="primary" @click="confirmUpload">确认</van-button>
                    <up-button @click="showUpload = false">取消</up-button>
                    <up-button type="primary" @click="confirmUpload">确认</up-button>
                </view>
            </view>
        </van-popup>
        </up-transition></up-popup>
        <!-- é™„件列表选择 -->
        <van-action-sheet v-model:show="showFileSheet" :actions="fileActions" cancel-text="取消" close-on-click-action @select="onSelectFile" />
        <up-action-sheet v-model="showFileSheet" :actions="fileActions" cancel-text="取消" close-on-click-action @select="onSelectFile">
            <view class="up-action-sheet__cancel" @click="showFileSheet = false">
                å–消
            </view>
        </up-action-sheet>
    </view>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import dayjs from 'dayjs'
import PageHeader from '@/components/PageHeader.vue'
const showToast = (message) => {
    uni.showToast({
        title: message,
        icon: 'none'
    })
}
const showLoadingToast = (message) => {
    uni.showLoading({
        title: message,
        mask: true
    })
}
const closeToast = () => {
    uni.hideLoading()
}
import useUserStore from '@/store/modules/user'
import { getToken } from '@/utils/auth'
@@ -282,7 +300,7 @@
const getList = async () => {
    try {
        showLoadingToast({ message: '加载中...' })
        showLoadingToast('加载中...')
        const { invoiceDate, ...rest } = searchForm
        const res = await registrationProductPage({ ...rest, ...page })
        // å…¼å®¹ä¸åŒè¿”回结构
@@ -313,19 +331,10 @@
    showFilter.value = false
    getList()
}
const onInvoiceRangeConfirm = (e) => {
    // e ä¸º [start, end] çš„ Date å¯¹è±¡æˆ–字符串,uni-app ä¸‹ Vant Calendar è¿”回时间戳数组
const onInvoiceRangeConfirm = ({ selectedValues }) => {
    try {
        let start, end
        if (Array.isArray(e)) {
            const [s, ed] = e
            start = dayjs(s).format('YYYY-MM-DD')
            end = dayjs(ed).format('YYYY-MM-DD')
        } else if (e && e.detail && Array.isArray(e.detail)) {
            const [s, ed] = e.detail
            start = dayjs(s).format('YYYY-MM-DD')
            end = dayjs(ed).format('YYYY-MM-DD')
        }
        const start = dayjs(selectedValues[0]).format('YYYY-MM-DD')
        const end = dayjs(selectedValues[1]).format('YYYY-MM-DD')
        searchForm.invoiceDateStart = start
        searchForm.invoiceDateEnd = end
        invoiceRangeLabel.value = `${start} è‡³ ${end}`
@@ -362,7 +371,7 @@
        success: async (res) => {
            if (res.confirm) {
                try {
                    showLoadingToast({ message: '处理中...' })
                    showLoadingToast('处理中...')
                    await delInvoiceLedgerByRegProductId(row.id)
                    closeToast()
                    showToast('删除成功')
@@ -385,7 +394,7 @@
const confirmUpload = async () => {
    try {
        const payload = { fileList: fileList.value, id: currentId.value }
        showLoadingToast({ message: '提交中...' })
        showLoadingToast('提交中...')
        await commitFile(payload)
        closeToast()
        showToast('提交成功')
@@ -420,7 +429,7 @@
const uploadSingleFile = async (fileObj) => {
    return new Promise((resolve, reject) => {
        showLoadingToast({ message: '正在上传...' })
        showLoadingToast('正在上传...')
        uni.uploadFile({
            url: config.baseUrl + '/invoiceLedger/uploadFile',
            filePath: fileObj.url || fileObj.file?.path || fileObj.tempFilePath,
@@ -496,7 +505,7 @@
    try {
        const item = currentFilesToOpen[action.index]
        if (!item || !item.url) return
        showLoadingToast({ message: '下载中...' })
        showLoadingToast('下载中...')
        uni.downloadFile({
            url: item.url,
            success: (res) => {
src/pages/sales/invoicingRegistration/add.vue
@@ -4,72 +4,70 @@
    <PageHeader title="新增开票登记" @back="goBack" />
    
    <!-- è¡¨å•内容 -->
    <van-form @submit="submitForm" ref="formRef" label-width="110px" input-align="right" error-message-align="right" scroll-to-error scroll-to-error-position="center">
    <up-form @submit="submitForm" ref="formRef" label-width="130" :rules="rules" :model="form">
      <!-- åŸºæœ¬ä¿¡æ¯ -->
      <van-cell-group title="基本信息" inset>
        <van-field
          v-model="form.salesContractNo"
          label="销售合同号"
          readonly
          placeholder="自动填充"
        />
        <van-field
          v-model="form.customerName"
          label="客户名称"
          readonly
          placeholder="自动填充"
        />
        <van-field
          v-model="form.salesman"
          label="业务员"
          readonly
          placeholder="自动填充"
        />
        <van-field
          v-model="form.projectName"
          label="项目名称"
          readonly
          placeholder="自动填充"
        />
        <van-field
          v-model="form.createUer"
          label="录入人"
                    readonly
          placeholder="请输入录入人"
        />
                <van-field
                    v-model="form.createTime"
                    label="录入日期"
                    readonly
                    placeholder="请选择录入日期"
                    @click="showCreateTimePicker = true"
                />
                <van-field
                    v-model="form.invoiceNo"
                    label="发票号码"
                    required
                    placeholder="请输入发票号码"
                    :rules="[{ required: true, message: '请输入发票号码' }]"
                />
        <van-field
          v-model="form.issueDate"
          label="开票日期"
          readonly
          placeholder="请选择开票日期"
                    required
      <view class="form-section">
        <up-form-item label="销售合同号" prop="salesContractNo">
          <up-input v-model="form.salesContractNo" placeholder="自动填充" disabled />
        </up-form-item>
        <up-form-item label="客户名称" prop="customerName">
          <up-input v-model="form.customerName" placeholder="自动填充" disabled />
        </up-form-item>
        <up-form-item label="业务员" prop="salesman">
          <up-input v-model="form.salesman" placeholder="自动填充" disabled />
        </up-form-item>
        <up-form-item label="项目名称" prop="projectName">
          <up-input v-model="form.projectName" placeholder="自动填充" disabled />
        </up-form-item>
        <up-form-item label="录入人" prop="createUer">
          <up-input v-model="form.createUer" placeholder="请输入录入人" disabled />
        </up-form-item>
        <up-form-item
          label="录入日期"
          prop="createTime"
          @click="showCreateTimePicker = true"
        >
          <up-input
            v-model="form.createTime"
            placeholder="请选择录入日期"
            readonly
            @click="showCreateTimePicker = true"
          />
          <template #right>
            <up-icon name="arrow-right" @click="showCreateTimePicker = true"></up-icon>
          </template>
        </up-form-item>
        <up-form-item label="发票号码" prop="invoiceNo" required>
          <up-input v-model="form.invoiceNo" placeholder="请输入发票号码" />
        </up-form-item>
        <up-form-item
          label="开票日期"
          prop="issueDate"
          required
          @click="showIssueDatePicker = true"
          :rules="[{ required: true, message: '请选择开票日期' }]"
        />
      </van-cell-group>
        >
          <up-input
            v-model="form.issueDate"
            placeholder="请选择开票日期"
            readonly
            @click="showIssueDatePicker = true"
          />
          <template #right>
            <up-icon name="arrow-right" @click="showIssueDatePicker = true"></up-icon>
          </template>
        </up-form-item>
      </view>
      <!-- äº§å“ä¿¡æ¯ -->
      <view class="product-section">
        <view class="section-header">
          <text class="section-title">产品信息</text>
          <view>
            <text class="section-title">产品信息</text>
          </view>
        </view>
        
        <view v-if="productData.length === 0" class="empty-state">
          <van-empty description="暂无产品数据" />
          <view class="empty-text">暂无产品数据</view>
        </view>
        
        <view v-else class="product-list">
@@ -81,116 +79,105 @@
            <!-- äº§å“å¤´éƒ¨ -->
            <view class="product-header">
              <view class="product-title">
                <van-icon name="description" color="#2979ff" size="15" />
                <view class="document-icon">
                  <up-icon name="file-text" size="16" color="#ffffff"></up-icon>
                </view>
                <text class="product-productCategory">产品 {{ index + 1 }}</text>
              </view>
            </view>
            
            <!-- äº§å“ä¿¡æ¯è¡¨å• -->
            <view class="product-form">
              <van-field
                v-model="item.productCategory"
                label="产品大类"
                readonly
              />
              <van-field
                v-model="item.specificationModel"
                label="规格型号"
                readonly
              />
              <van-field
                v-model="item.unit"
                label="单位"
                readonly
              />
              <van-field
                v-model="item.quantity"
                label="数量"
                readonly
              />
              <van-field
                v-model="item.taxRate"
                                label="税率(%)"
                readonly
              />
              <van-field
                v-model="item.taxInclusiveUnitPrice"
                label="含税单价(元)"
                readonly
              />
              <van-field
                v-model="item.taxInclusiveTotalPrice"
                label="含税总价(元)"
                readonly
              />
              <van-field
                v-model="item.taxExclusiveTotalPrice"
                label="不含税总价(元)"
                readonly
              />
              <up-form-item label="产品大类" prop="productCategory">
                <up-input v-model="item.productCategory" placeholder="" disabled />
              </up-form-item>
              <up-form-item label="规格型号" prop="specificationModel">
                <up-input v-model="item.specificationModel" placeholder="" disabled />
              </up-form-item>
              <up-form-item label="单位" prop="unit">
                <up-input v-model="item.unit" placeholder="" disabled />
              </up-form-item>
              <up-form-item label="数量" prop="quantity">
                <up-input v-model="item.quantity" placeholder="" disabled />
              </up-form-item>
              <up-form-item label="税率(%)" prop="taxRate">
                <up-input v-model="item.taxRate" placeholder="" disabled />
              </up-form-item>
              <up-form-item label="含税单价(元)" prop="taxInclusiveUnitPrice">
                <up-input v-model="item.taxInclusiveUnitPrice" placeholder="" disabled />
              </up-form-item>
              <up-form-item label="含税总价(元)" prop="taxInclusiveTotalPrice">
                <up-input v-model="item.taxInclusiveTotalPrice" placeholder="" disabled />
              </up-form-item>
              <up-form-item label="不含税总价(元)" prop="taxExclusiveTotalPrice">
                <up-input v-model="item.taxExclusiveTotalPrice" placeholder="" disabled />
              </up-form-item>
              
              <!-- æœ¬æ¬¡å¼€ç¥¨ä¿¡æ¯ -->
              <van-field
                v-model="item.currentInvoiceNum"
                label="本次开票数"
                type="number"
                placeholder="请输入开票数量"
                @blur="invoiceNumBlur(item)"
              />
              <van-field
                v-model="item.currentInvoiceAmount"
                label="本次开票金额(元)"
                type="number"
                placeholder="请输入开票金额"
                @blur="invoiceAmountBlur(item)"
              />
              <up-form-item label="本次开票数" prop="currentInvoiceNum">
                <up-input
                  v-model="item.currentInvoiceNum"
                  type="number"
                  placeholder="请输入开票数量"
                  @blur="invoiceNumBlur(item)"
                />
              </up-form-item>
              <up-form-item label="本次开票金额(元)" prop="currentInvoiceAmount">
                <up-input
                  v-model="item.currentInvoiceAmount"
                  type="number"
                  placeholder="请输入开票金额"
                  @blur="invoiceAmountBlur(item)"
                />
              </up-form-item>
              
              <!-- æœªå¼€ç¥¨ä¿¡æ¯ -->
              <van-field
                v-model="item.noInvoiceNum"
                label="未开票数"
                readonly
              />
              <van-field
                v-model="item.noInvoiceAmount"
                label="未开票金额(元)"
                readonly
              />
              <up-form-item label="未开票数" prop="noInvoiceNum">
                <up-input v-model="item.noInvoiceNum" placeholder="" disabled />
              </up-form-item>
              <up-form-item label="未开票金额(元)" prop="noInvoiceAmount">
                <up-input v-model="item.noInvoiceAmount" placeholder="" disabled />
              </up-form-item>
            </view>
          </view>
        </view>
      </view>
      <!-- æäº¤æŒ‰é’® -->
            <view class="footer-btns">
                <van-button class="cancel-btn" @click="goBack">取消</van-button>
                <van-button class="save-btn" native-type="submit" form-type="submit">保存</van-button>
            </view>
    </van-form>
      <!-- ä½¿ç”¨å…¬å…±åº•部按钮组件 -->
        <FooterButtons
            show
            cancelText="取消"
            confirmText="保存"
            @cancel="goBack"
            @confirm="submitForm"
        />
    </up-form>
    <!-- æ—¥æœŸé€‰æ‹©å™¨ -->
    <van-popup v-model:show="showIssueDatePicker" position="bottom">
      <van-date-picker
        v-model="currentIssueDate"
        title="选择开票日期"
    <up-popup :show="showIssueDatePicker" mode="bottom" @close="showIssueDatePicker = false">
      <up-datetime-picker
        :show="true"
        v-model="pickerIssueDateValue"
        @confirm="onIssueDateConfirm"
        @cancel="showIssueDatePicker = false"
        mode="date"
      />
    </van-popup>
    </up-popup>
    <van-popup v-model:show="showCreateTimePicker" position="bottom">
      <van-date-picker
        v-model="currentCreateTime"
        title="选择录入日期"
    <up-popup :show="showCreateTimePicker" mode="bottom" @close="showCreateTimePicker = false">
      <up-datetime-picker
        :show="true"
        v-model="pickerCreateTimeValue"
        @confirm="onCreateTimeConfirm"
        @cancel="showCreateTimePicker = false"
        mode="date"
      />
    </van-popup>
    </up-popup>
  </view>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ref, onMounted } from 'vue'
// æ›¿æ¢ toast æ–¹æ³•
const showToast = (message) => {
    uni.showToast({
@@ -211,6 +198,8 @@
import { invoiceRegistrationSave } from '@/api/salesManagement/invoiceRegistration'
import useUserStore from '@/store/modules/user'
import {getSalesLedgerWithProducts} from "@/api/salesManagement/salesLedger";
import FooterButtons from '@/components/FooterButtons.vue';
import { formatDateToYMD } from '@/utils/ruoyi'
const userStore = useUserStore()
const editData = ref(null);
@@ -230,14 +219,24 @@
  invoiceNo: ''
})
// è¡¨å•校验规则
const rules = {
  invoiceNo: [
    { required: true, message: '请输入发票号码', trigger: 'blur' }
  ],
  issueDate: [
    { required: true, message: '请选择开票日期', trigger: 'blur' }
  ]
};
// äº§å“æ•°æ®
const productData = ref([])
// æ—¥æœŸé€‰æ‹©å™¨çŠ¶æ€
const showIssueDatePicker = ref(false)
const showCreateTimePicker = ref(false)
const currentIssueDate = ref([new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()])
const currentCreateTime = ref([new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()])
const pickerIssueDateValue = ref(Date.now())
const pickerCreateTimeValue = ref(Date.now())
// æäº¤çŠ¶æ€
const submitting = ref(false)
@@ -314,42 +313,20 @@
}
// å¼€ç¥¨æ—¥æœŸç¡®è®¤
const onIssueDateConfirm = ({ selectedValues }) => {
    console.log('selectedValues--', selectedValues)
    form.value.issueDate = selectedValues.join('-');
    currentIssueDate.value = selectedValues;
const onIssueDateConfirm = (e) => {
    form.value.issueDate = formatDateToYMD(e.value)
    pickerIssueDateValue.value = e.value
    showIssueDatePicker.value = false;
};
// å½•入日期确认
const onCreateTimeConfirm = (value) => {
  try {
    // å¤„理不同的值格式
    let year, month, day;
    if (Array.isArray(value)) {
      // æ•°ç»„格式 [year, month, day]
      [year, month, day] = value;
    } else if (value && typeof value === 'object') {
      // Date对象格式
      year = value.getFullYear();
      month = value.getMonth() + 1;
      day = value.getDate();
    } else {
      // å…¶ä»–格式,使用当前日期
      const now = new Date();
      year = now.getFullYear();
      month = now.getMonth() + 1;
      day = now.getDate();
    }
    form.value.createTime = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
    showCreateTimePicker.value = false;
  } catch (error) {
    console.error('日期处理错误:', error);
    showToast('日期选择失败,请重试');
  }
}
const onCreateTimeConfirm = (e) => {
    form.value.createTime = formatDateToYMD(e.value)
    pickerCreateTimeValue.value = e.value
    showCreateTimePicker.value = false;
};
// æ ¼å¼åŒ–日期
const formatDate = (date) => {
@@ -441,106 +418,6 @@
})
</script>
<style scoped lang="scss">
.account-detail {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 5rem;
}
.empty-state {
  padding: 40px 0;
}
.product-section {
  background: #fff;
  margin-top: 1rem;
  padding: 1rem;
  box-shadow: 0 0.125rem 0.5rem rgba(0,0,0,0.04);
}
.section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1rem;
}
.section-title {
  font-size: 1rem;
  font-weight: 600;
  color: #333;
}
.product-list {
  .product-card {
    background: #FFFFFF;
    box-shadow: 0 0 1.25rem 0 rgba(0,57,117,0.08);
    border-radius: 0.5rem 0.5rem 0.5rem 0.5rem;
    padding: 1rem 0.5rem 0 0.5rem;
    position: relative;
    margin-bottom: 1rem;
  }
}
.product-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 0.5rem 0.75rem 0.5rem;
  border-bottom: 0.0625rem solid #e8e8e8;
}
.product-title {
  display: flex;
  align-items: center;
}
.product-productCategory {
  margin-left: 0.5rem;
  font-size: 0.875rem;
  font-weight: 500;
  color: #333;
}
.product-form {
  margin-bottom: 1rem;
}
.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;
  }
}
<style lang="scss">
@import '@/static/scss/form-common.scss';
</style>
src/pages/sales/invoicingRegistration/index.vue
@@ -66,23 +66,22 @@
                    
                    <!-- æ“ä½œæŒ‰é’®åŒºåŸŸ -->
                    <view class="action-buttons">
                        <van-button
                            type="primary"
                            size="small"
                            @click="handleAddInvoice(item)"
                            class="action-btn"
                            :disabled="item.noInvoiceAmountTotal == 0"
                        >
                            æ–°å¢žå¼€ç¥¨
                        </van-button>
                        <van-button
                            type="default"
                            size="small"
                            @click="handleViewDetail(item)"
                            class="action-btn"
                        >
                            æŸ¥çœ‹è¯¦æƒ…
                        </van-button>
                        <up-button
            type="primary"
            size="small"
            @click="handleAddInvoice(item)"
            class="action-btn"
            :disabled="item.noInvoiceAmountTotal == 0"
        >
            æ–°å¢žå¼€ç¥¨
        </up-button>
        <up-button
            size="small"
            @click="handleViewDetail(item)"
            class="action-btn"
        >
            æŸ¥çœ‹è¯¦æƒ…
        </up-button>
                    </view>
                </view>
            </view>
src/pages/sales/invoicingRegistration/view.vue
@@ -13,7 +13,7 @@
        </view>
        <view class="info-item">
          <text class="info-label">客户合同号</text>
          <text class="info-value">{{ form.customerContractNo }}</text>
          <text class="info-value highlight">{{ form.customerContractNo }}</text>
        </view>
        <view class="info-item">
          <text class="info-label">客户名称</text>
@@ -37,7 +37,7 @@
        </view>
        <view class="info-item">
          <text class="info-label">未开票金额(元)</text>
          <text class="info-value redlight">{{ form.noInvoiceAmountTotal }}</text>
          <text class="info-value highlight">{{ form.noInvoiceAmountTotal }}</text>
        </view>
      </view>
    </view>
@@ -48,7 +48,8 @@
      <view class="product-card" v-for="(product, idx) in productData" :key="idx">
        <view class="product-header">
          <view class="product-title">
            <van-icon name="description" color="#2979ff" size="15" />
            <!-- ä½¿ç”¨u-icon替代up-icon -->
            <u-icon name="file-text" color="#2979ff" size="15" />
            <text class="product-productCategory">产品 {{ idx + 1 }}</text>
          </view>
        </view>
@@ -75,10 +76,10 @@
              <text class="info-label">数量</text>
              <text class="info-value highlight">{{ product.quantity }}</text>
            </view>
                        <view class="info-item">
                            <text class="info-label">含税单价(元)</text>
                            <text class="info-value highlight">{{ product.taxInclusiveUnitPrice }}</text>
                        </view>
            <view class="info-item">
              <text class="info-label">含税单价(元)</text>
              <text class="info-value highlight">{{ product.taxInclusiveUnitPrice }}</text>
            </view>
            <view class="info-item">
              <text class="info-label">含税总价(元)</text>
              <text class="info-value highlight">{{ product.taxInclusiveTotalPrice }}</text>
@@ -101,23 +102,22 @@
            </view>
            <view class="info-item">
              <text class="info-label">未开票金额(元)</text>
              <text class="info-value redlight">{{ product.noInvoiceAmount }}</text>
              <text class="info-value highlight">{{ product.noInvoiceAmount }}</text>
            </view>
          </view>
        </view>
      </view>
    </view>
    <!-- æ— äº§å“ä¿¡æ¯æç¤º -->
    <view class="no-product" v-else>
    <view v-else class="no-product">
      <text>暂无产品信息</text>
    </view>
  </view>
</template>
<script setup>
import { onMounted, ref } from 'vue';
import { getSalesLedgerWithProducts } from "@/api/salesManagement/salesLedger";
import {onMounted, ref} from 'vue';
import {getSalesLedgerWithProducts} from "@/api/salesManagement/salesLedger";
// è¡¨å•数据
const form = ref({
@@ -154,20 +154,8 @@
  // èŽ·å–å®Œæ•´çš„äº§å“ä¿¡æ¯
  getSalesLedgerWithProducts({ id: editData.value.id, type: 1 }).then((res) => {
    productData.value = res.productData || [];
    form.value = {...editData.value}
  });
  // å¡«å……基本信息
  form.value.salesContractNo = editData.value.salesContractNo || '';
  form.value.customerContractNo = editData.value.customerContractNo || '';
  form.value.customerName = editData.value.customerName || '';
  form.value.projectName = editData.value.projectName || '';
  form.value.executionDate = editData.value.executionDate || '';
  form.value.contractAmount = editData.value.contractAmount || '';
  form.value.salesman = editData.value.salesman || '';
  form.value.invoiceTotal = editData.value.invoiceTotal || 0;
  form.value.noInvoiceAmountTotal = editData.value.noInvoiceAmountTotal || 0;
  form.value.id = editData.value.id || '';
  form.value.customerId = editData.value.customerId || '';
};
onMounted(() => {
@@ -203,8 +191,6 @@
  position: sticky;
  top: 0;
  z-index: 100;
  /* å…¼å®¹ iOS åˆ˜æµ·/灵动岛安全区 */
  padding-top: env(safe-area-inset-top);
}
.title {
@@ -223,12 +209,20 @@
  box-shadow: 0 0.125rem 0.5rem rgba(0,0,0,0.04);
}
.product-section {
  background: #fff;
  margin: 1rem;
  padding: 1rem;
  border-radius: 0.5rem;
  box-shadow: 0 0.125rem 0.5rem rgba(0,0,0,0.04);
}
.section-title {
  font-size: 1rem;
  font-weight: 600;
  color: #333;
  margin-bottom: 1rem;
  padding-bottom: 1rem;
  padding-bottom: 0.5rem;
  border-bottom: 0.0625rem solid #e8e8e8;
}
@@ -260,36 +254,22 @@
  color: #2979ff;
  font-weight: 600;
}
.info-value.redlight {
  color: red;
  font-weight: 600;
}
.product-section {
  background: #fff;
  margin: 1rem;
  padding: 1rem;
  border-radius: 0.5rem;
  box-shadow: 0 0.125rem 0.5rem rgba(0,0,0,0.04);
}
.product-card {
  background: #f8f9fa;
  border-radius: 0.5rem;
  padding: 1rem;
  margin-bottom: 1rem;
}
.product-card:last-child {
  margin-bottom: 0;
  border: 0.0625rem solid #e8e8e8;
}
.product-header {
  display: flex;
  align-items: center;
  padding-bottom: 0.75rem;
  border-bottom: 0.0625rem solid #e8e8e8;
  justify-content: space-between;
  margin-bottom: 1rem;
  padding-bottom: 0.5rem;
  border-bottom: 0.0625rem solid #e8e8e8;
}
.product-title {
src/pages/sales/receiptPayment/add.vue
@@ -59,6 +59,12 @@
            readonly
            @click="showPaymentTypePicker"
          />
          <template #right>
                    <up-icon
                        name="arrow-right"
                        @click="showPaymentTypePicker"
                    ></up-icon>
                </template>
        </u-form-item>
        <u-form-item label="来款日期" prop="receiptPaymentDate" required border-bottom>
          <u-input
@@ -67,6 +73,12 @@
            readonly
            @click="showDatePicker"
          />
          <template #right>
                    <up-icon
                        name="arrow-right"
                        @click="showDatePicker"
                    ></up-icon>
                </template>
        </u-form-item>
        <u-form-item label="登记人" border-bottom>
          <u-input
@@ -78,41 +90,46 @@
      </u-cell-group>
      
      <!-- æäº¤æŒ‰é’® -->
      <view class="footer-btns">
        <u-button class="cancel-btn" @click="onClickLeft">取消</u-button>
        <u-button class="save-btn" type="primary" @click="onSubmit" :loading="loading">保存</u-button>
      </view>
      <FooterButtons
        cancelText="取消"
        confirmText="保存"
        :loading="loading"
        @cancel="onClickLeft"
        @confirm="onSubmit"
      />
    </u-form>
    <!-- å›žæ¬¾æ–¹å¼é€‰æ‹©å™¨ -->
    <u-popup v-model="showPaymentType" mode="bottom">
      <u-picker
        v-model="pickerValue"
        :columns="receipt_payment_type"
        @confirm="onPaymentTypeConfirm"
        @cancel="showPaymentType = false"
      />
    </u-popup>
    <up-action-sheet
      :show="showPaymentType"
      :actions="receiptPaymentType"
      title="选择回款形式"
      @select="onPaymentTypeConfirm"
      @close="showPaymentType = false"
    />
    <!-- æ—¥æœŸé€‰æ‹©å™¨ -->
    <u-popup v-model="showDate" mode="bottom">
      <u-datetime-picker
    <up-popup :show="showDate" mode="bottom" @close="showDate = false">
      <up-datetime-picker
        :show="true"
        v-model="currentDate"
        title="选择日期"
        @confirm="onDateConfirm"
        @cancel="showDate = false"
        mode="date"
      />
    </u-popup>
    </up-popup>
  </view>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue'
import FooterButtons from '@/components/FooterButtons.vue'
import { receiptPaymentSaveOrUpdate, invoiceInfo } from '@/api/salesManagement/receiptPayment'
import useUserStore from '@/store/modules/user'
import { useDict } from '@/utils/dict'
import { formatDateToYMD } from '@/utils/ruoyi'
// æ›¿æ¢ toast å’Œ notify æ–¹æ³•
// æ˜¾ç¤ºæç¤ºä¿¡æ¯
const showToast = (message) => {
  uni.showToast({
    title: message,
@@ -120,12 +137,18 @@
  })
}
const showNotify = ({ type, message }) => {
  uni.showToast({
// æ˜¾ç¤ºåŠ è½½æç¤º
const showLoadingToast = (message) => {
  uni.showLoading({
    title: message,
    icon: type === 'warning' ? 'none' : 'success'
  })
}
    mask: true
  });
};
// å…³é—­åŠ è½½æç¤º
const closeToast = () => {
  uni.hideLoading();
};
const userStore = useUserStore()
@@ -159,9 +182,9 @@
const { receipt_payment_type: dictReceiptPaymentType } = useDict('receipt_payment_type')
// è½¬æ¢å­—典数据格式为选择器需要的格式
const receipt_payment_type = computed(() => {
const receiptPaymentType = computed(() => {
  return dictReceiptPaymentType.value.map(item => ({
    text: item.label,
    name: item.label,
    value: item.value
  }))
})
@@ -184,10 +207,9 @@
}
// ç¡®è®¤å›žæ¬¾æ–¹å¼é€‰æ‹©
const onPaymentTypeConfirm = ({ selectedValues, selectedOptions }) => {
  form.value.receiptPaymentType = selectedOptions[0].value
  form.value.receiptPaymentTypeName = selectedOptions[0].text
    pickerValue.value = selectedValues;
const onPaymentTypeConfirm = (action) => {
  form.value.receiptPaymentType = action.value
  form.value.receiptPaymentTypeName = action.name
  showPaymentType.value = false
}
@@ -197,27 +219,27 @@
}
// ç¡®è®¤æ—¥æœŸé€‰æ‹©
const onDateConfirm = ({ selectedValues }) => {
  form.value.receiptPaymentDate = selectedValues.join('-')
    currentDate.value = selectedValues
  showDate.value = false
const onDateConfirm = (e) => {
    form.value.receiptPaymentDate = formatDateToYMD(e.value)
    currentDate.value = formatDateToYMD(e.value)
    showDate.value = false;
}
// æäº¤è¡¨å•
const onSubmit = () => {
  // è¡¨å•验证
  if (!form.value.receiptPaymentAmount) {
    showNotify({ type: 'warning', message: '请输入回款金额' })
    showToast('请输入回款金额')
    return
  }
  
  if (!form.value.receiptPaymentType) {
    showNotify({ type: 'warning', message: '请选择回款形式' })
    showToast('请选择回款形式')
    return
  }
  
  if (!form.value.receiptPaymentDate) {
    showNotify({ type: 'warning', message: '请选择来款日期' })
    showToast('请选择来款日期')
    return
  }
@@ -262,51 +284,5 @@
</script>
<style scoped lang="scss">
.account-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; }
@import '@/static/scss/form-common.scss';
</style>
src/pages/sales/receiptPayment/edit.vue
@@ -59,13 +59,26 @@
                        readonly
                        @click="showPaymentTypePicker"
                    />
                    <template #right>
                    <up-icon
                        name="arrow-right"
                        @click="showPaymentTypePicker"
                    ></up-icon>
                </template>
                </u-form-item>
                <u-form-item label="来款日期" prop="receiptPaymentDate" required border-bottom>
                    <u-input
                        v-model="form.receiptPaymentDate"
                        placeholder="请选择"
                        readonly
                        @click="showDatePicker"
                    />
                    <template #right>
                    <up-icon
                        name="arrow-right"
                        @click="showDatePicker"
                    ></up-icon>
                </template>
                </u-form-item>
                <u-form-item label="登记人" border-bottom>
                    <u-input
@@ -77,41 +90,46 @@
            </u-cell-group>
            
            <!-- æäº¤æŒ‰é’® -->
            <view class="footer-btns">
                <u-button class="cancel-btn" @click="onClickLeft">取消</u-button>
                <u-button class="save-btn" type="primary" @click="onSubmit" :loading="loading">保存</u-button>
            </view>
            <FooterButtons
                cancelText="取消"
                confirmText="保存"
                :loading="loading"
                @cancel="onClickLeft"
                @confirm="onSubmit"
            />
        </u-form>
        
        <!-- å›žæ¬¾æ–¹å¼é€‰æ‹©å™¨ -->
        <u-popup v-model="showPaymentType" mode="bottom">
            <u-picker
                v-model="pickerValue"
                :columns="receipt_payment_type"
                @confirm="onPaymentTypeConfirm"
                @cancel="showPaymentType = false"
            />
        </u-popup>
        <up-action-sheet
            :show="showPaymentType"
            :actions="receipt_payment_type"
            title="选择回款形式"
            @select="onPaymentTypeConfirm"
            @close="showPaymentType = false"
        />
        
        <!-- æ—¥æœŸé€‰æ‹©å™¨ -->
        <u-popup v-model="showDate" mode="bottom">
            <u-datetime-picker
        <up-popup :show="showDate" mode="bottom" @close="showDate = false">
            <up-datetime-picker
                :show="true"
                v-model="currentDate"
                title="选择日期"
                @confirm="onDateConfirm"
                @cancel="showDate = false"
                mode="date"
            />
        </u-popup>
        </up-popup>
    </view>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue'
import FooterButtons from '@/components/FooterButtons.vue'
import { receiptPaymentSaveOrUpdate, invoiceInfo } from '@/api/salesManagement/receiptPayment'
import useUserStore from '@/store/modules/user'
import { useDict } from '@/utils/dict'
import { formatDateToYMD } from '@/utils/ruoyi'
// æ›¿æ¢ toast å’Œ notify æ–¹æ³•
// æ˜¾ç¤ºæç¤ºä¿¡æ¯
const showToast = (message) => {
  uni.showToast({
    title: message,
@@ -119,12 +137,18 @@
  })
}
const showNotify = ({ type, message }) => {
  uni.showToast({
// æ˜¾ç¤ºåŠ è½½æç¤º
const showLoadingToast = (message) => {
  uni.showLoading({
    title: message,
    icon: type === 'warning' ? 'none' : 'success'
  })
}
    mask: true
  });
};
// å…³é—­åŠ è½½æç¤º
const closeToast = () => {
  uni.hideLoading();
};
const userStore = useUserStore()
@@ -160,7 +184,7 @@
// è½¬æ¢å­—典数据格式为选择器需要的格式
const receipt_payment_type = computed(() => {
    return dictReceiptPaymentType.value.map(item => ({
        text: item.label,
        name: item.label,
        value: item.value
    }))
})
@@ -183,10 +207,9 @@
}
// ç¡®è®¤å›žæ¬¾æ–¹å¼é€‰æ‹©
const onPaymentTypeConfirm = ({ selectedValues, selectedOptions }) => {
    form.value.receiptPaymentType = selectedOptions[0].value
    form.value.receiptPaymentTypeName = selectedOptions[0].text
    pickerValue.value = selectedValues;
const onPaymentTypeConfirm = (action) => {
    form.value.receiptPaymentType = action.value
    form.value.receiptPaymentTypeName = action.name
    showPaymentType.value = false
}
@@ -196,22 +219,22 @@
}
// ç¡®è®¤æ—¥æœŸé€‰æ‹©
const onDateConfirm = ({ selectedValues }) => {
    form.value.receiptPaymentDate = selectedValues.join('-')
    currentDate.value = selectedValues
    showDate.value = false
const onDateConfirm = (e) => {
    form.value.receiptPaymentDate = formatDateToYMD(e.value)
    currentDate.value = formatDateToYMD(e.value)
    showDate.value = false;
}
// æäº¤è¡¨å•
const onSubmit = () => {
    // è¡¨å•验证
    if (!form.value.receiptPaymentAmount) {
        showNotify({ type: 'warning', message: '请输入回款金额' })
        showToast('请输入回款金额')
        return
    }
    
    if (!form.value.receiptPaymentType) {
        showNotify({ type: 'warning', message: '请选择回款形式' })
        showToast('请选择回款形式')
        return
    }
    loading.value = true
@@ -254,51 +277,11 @@
</script>
<style scoped lang="scss">
@import '@/static/scss/form-common.scss';
.account-detail {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 5rem;
  min-height: 100vh;
  background: #f5f5f5;
  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/sales/receiptPayment/index.vue
@@ -1,5 +1,5 @@
<template>
    <view class="receipt-payment">
    <view class="sales-account">
        <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
        <PageHeader title="回款登记" @back="goBack" />
@@ -22,7 +22,7 @@
            <!-- ç­›é€‰å¼€å…³ -->
            <view class="switch-row">
                <text class="switch-label">不显示待回款为0</text>
                <van-switch v-model="searchForm.status" @change="getList" size="18"/>
                <up-switch v-model="searchForm.status" @change="getList" size="18"/>
            </view>
        </view>
@@ -82,7 +82,7 @@
                    
                    <!-- æ“ä½œæŒ‰é’® -->
                    <view class="action-buttons">
                        <van-button
                        <up-button
                            type="primary"
                            size="small"
                            class="action-btn"
@@ -90,7 +90,7 @@
                            @click="openForm(item)"
                        >
                            æ–°å¢žå›žæ¬¾
                        </van-button>
                        </up-button>
                    </view>
                </view>
            </view>
@@ -109,12 +109,28 @@
    bindInvoiceNoRegPage,
} from '@/api/salesManagement/receiptPayment'
import useUserStore from '@/store/modules/user'
// æ˜¾ç¤ºæç¤ºä¿¡æ¯
const showToast = (message) => {
    uni.showToast({
        title: message,
        icon: 'none'
    })
}
// æ˜¾ç¤ºåŠ è½½æç¤º
const showLoadingToast = (message) => {
    uni.showLoading({
        title: message,
        mask: true
    });
};
// å…³é—­åŠ è½½æç¤º
const closeToast = () => {
    uni.hideLoading();
};
import {onShow} from "@dcloudio/uni-app";
// å“åº”式数据
@@ -148,14 +164,16 @@
// èŽ·å–åˆ—è¡¨æ•°æ®
const getList = () => {
    tableLoading.value = true
    showLoadingToast('加载中...')
    bindInvoiceNoRegPage({ ...searchForm.value, ...page.value })
        .then((res) => {
            tableLoading.value = false
            tableData.value = res.data.records || []
        })
        .catch(() => {
            tableLoading.value = false
            showToast('获取数据失败')
        })
        .finally(() => {
            closeToast()
        })
}
@@ -179,7 +197,7 @@
    margin: 0 !important;
}
.receipt-payment {
.sales-account {
    min-height: 100vh;
    background: #f8f9fa;
    position: relative;
src/pages/sales/receiptPaymentHistory/index.vue
@@ -80,7 +80,7 @@
                        </view>
                        <!-- æ“ä½œæŒ‰é’® -->
                        <view class="action-buttons">
                            <van-button
                            <u-button
                                type="primary"
                                size="small"
                                class="action-btn"
@@ -88,7 +88,7 @@
                                @click="openForm(item)"
                            >
                                ç¼–辑回款
                            </van-button>
                            </u-button>
                        </view>
                    </view>
                </view>
src/pages/sales/receiptPaymentLedger/detail.vue
@@ -115,6 +115,8 @@
    
    const param = {
        customerId: customerId.value,
        current: -1,
        size: -1
    };
    
    customerInteractions(param).then((res) => {
src/pages/sales/salesAccount/detail.vue
@@ -4,110 +4,120 @@
        <PageHeader title="台账详情" @back="goBack" />
         <!-- è¡¨å•区域 -->
        <u-form @submit="onSubmit" label-width="110" input-align="right" style="margin-top: 10px" error-message-align="right">
            <u-form-item label="销售合同号" prop="salesContractNo" border-bottom>
                <u-input v-model="form.salesContractNo" placeholder="自动生成" disabled />
            </u-form-item>
            <u-form-item
        <up-form @submit="onSubmit" label-width="110" ref="formRef" :rules="rules" :model="form">
            <up-form-item label="销售合同号" prop="salesContractNo" >
                <up-input v-model="form.salesContractNo" placeholder="自动生成" disabled />
            </up-form-item>
            <up-form-item
                label="业务员"
                prop="salesman"
                required
                border-bottom
                @click="showPicker = true"
            >
                <u-input
                <up-input
                    v-model="form.salesman"
                    readonly
                    placeholder="点击选择业务员"
                    readonly=""
                    @click="showPicker = true"
                    placeholder="点击选择业务员"
                />
            </u-form-item>
            <u-form-item label="客户合同号" prop="customerContractNo" required border-bottom>
                <u-input
                <template #right>
                    <up-icon
                        name="arrow-right"
                        @click="showPicker = true"
                    ></up-icon>
                </template>
            </up-form-item>
            <up-form-item label="客户合同号" prop="customerContractNo" required >
                <up-input
                    v-model="form.customerContractNo"
                    placeholder="请输入客户合同号"
                />
            </u-form-item>
            <u-form-item
            </up-form-item>
            <up-form-item
                label="客户名称"
                prop="customerName"
                required
                border-bottom
            >
                <u-input
                <up-input
                    v-model="form.customerName"
                    readonly
                    placeholder="点击选择客户"
                    @click="showCustomerPicker = true"
                />
            </u-form-item>
            <u-form-item label="项目名称" prop="projectName" required border-bottom>
                <u-input v-model="form.projectName" placeholder="请输入项目名称" />
            </u-form-item>
            <u-form-item
                <template #right>
                    <up-icon
                        name="arrow-right"
                        @click="showCustomerPicker = true"
                    ></up-icon>
                </template>
            </up-form-item>
            <up-form-item label="项目名称" prop="projectName" required >
                <up-input v-model="form.projectName" placeholder="请输入项目名称" />
            </up-form-item>
            <up-form-item
                label="签订日期"
                prop="executionDate"
                required
                border-bottom
            >
                <u-input
                <up-input
                    v-model="form.executionDate"
                    readonly
                    placeholder="点击选择时间"
                    @click="showDatePicker = true"
                />
            </u-form-item>
            <u-popup v-model="showDatePicker" mode="bottom">
                <u-datetime-picker
                <template #right>
                    <up-icon
                        name="arrow-right"
                        @click="showDatePicker = true"
                    ></up-icon>
                </template>
            </up-form-item>
            <up-form-item label="付款方式" prop="paymentMethod" >
                <up-input v-model="form.paymentMethod" placeholder="请输入付款方式" />
            </up-form-item>
            <up-form-item label="录入人" prop="entryPersonName" >
                <up-input v-model="form.entryPersonName" placeholder="请输入" disabled />
            </up-form-item>
            <up-form-item label="录入日期" prop="entryDate" >
                <up-input v-model="form.entryDate" placeholder="请输入" disabled />
            </up-form-item>
            <!-- ä¸šåŠ¡å‘˜é€‰æ‹© -->
            <up-action-sheet
                :show="showPicker"
                :actions="userActionList"
                title="选择业务员"
                @select="onSalesmanSelect"
                @close="showPicker = false"
            />
            <!-- æ—¥æœŸé€‰æ‹© -->
            <up-popup :show="showDatePicker" mode="bottom" @close="showDatePicker = false">
                <up-datetime-picker
                    :show="true"
                    v-model="pickerDateValue"
                    @confirm="onDateConfirm"
                    @cancel="showDatePicker = false"
                    mode="date"
                />
            </u-popup>
            <u-form-item label="付款方式" prop="paymentMethod" border-bottom>
                <u-input v-model="form.paymentMethod" placeholder="请输入付款方式" />
            </u-form-item>
            <u-form-item label="录入人" prop="entryPersonName" border-bottom>
                <u-input v-model="form.entryPersonName" placeholder="请输入" disabled />
            </u-form-item>
            <u-form-item label="录入日期" prop="entryDate" border-bottom>
                <u-input v-model="form.entryDate" placeholder="请输入" disabled />
            </u-form-item>
            <!-- ä¸šåŠ¡å‘˜é€‰æ‹©å¼¹çª— -->
            <u-popup v-model="showPicker" mode="bottom">
                <view class="picker-header">
                    <view class="picker-cancel" @click="showPicker = false">取消</view>
                    <view class="picker-title">选择业务员</view>
                    <view class="picker-confirm" @click="confirmSalesman">确定</view>
                </view>
                <u-picker
                    :columns="userList"
                    v-model="pickerValue"
                    @change="onPickerChange"
                />
            </u-popup>
            <!-- å®¢æˆ·é€‰æ‹©å¼¹çª— -->
            <u-popup v-model="showCustomerPicker" mode="bottom">
                <view class="picker-header">
                    <view class="picker-cancel" @click="showCustomerPicker = false">取消</view>
                    <view class="picker-title">选择客户</view>
                    <view class="picker-confirm" @click="confirmCustomer">确定</view>
                </view>
                <u-picker
                    :columns="customerOption"
                    v-model="pickerCustomerValue"
                    @change="onCustomerPickerChange"
                />
            </u-popup>
            </up-popup>
            <!-- å®¢æˆ·é€‰æ‹© -->
            <up-action-sheet
                :show="showCustomerPicker"
                :actions="customerActionList"
                title="选择客户"
                @select="onCustomerSelect"
                @close="showCustomerPicker = false"
            />
            
            <!-- äº§å“å¤§ç±»é€‰æ‹©å™¨ -->
            <u-popup v-model="showCategoryPicker" mode="bottom">
            <up-popup :show="showCategoryPicker" mode="bottom">
                <!-- å¤´éƒ¨æŒ‰é’®åŒºåŸŸ -->
                <view class="popup-header">
                    <view @click="showCategoryPicker = false" class="cancelButton">取消</view>
                    <view @click="confirmCategorySelection" class="confirmButton">确定</view>
                </view>
                <up-tree
                <u-tree
                    :data="productOptions"
                    :props="defaultProps"
                    show-checkbox
@@ -115,200 +125,225 @@
                    check-strictly
                    @check-change="onCategoryConfirm"
                />
            </u-popup>
            </up-popup>
            
            <!-- è§„格型号选择器 -->
            <u-popup v-model="showSpecificationPicker" mode="bottom">
                <u-picker
                    :columns="modelOptions"
                    v-model="pickerSpecificationValue"
                    @confirm="onSpecificationConfirm"
                    @cancel="showSpecificationPicker = false"
                />
            </u-popup>
            <up-action-sheet
                :show="showSpecificationPicker"
                :actions="specificationActionList"
                title="选择规格型号"
                @select="onSpecificationSelect"
                @close="showSpecificationPicker = false"
            />
            
            <!-- ç¨ŽçŽ‡é€‰æ‹©å™¨ -->
            <u-popup v-model="showTaxRatePicker" mode="bottom">
                <u-picker
                    :columns="taxRateOptions"
                    v-model="pickerTaxRateValue"
                    @confirm="onTaxRateConfirm"
                    @cancel="showTaxRatePicker = false"
                />
            </u-popup>
            <up-action-sheet
                :show="showTaxRatePicker"
                :actions="taxRateActionList"
                title="选择税率"
                @select="onTaxRateSelect"
                @close="showTaxRatePicker = false"
            />
            
            <!-- å‘票类型选择器 -->
            <u-popup v-model="showInvoiceTypePicker" mode="bottom">
                <u-picker
                    :columns="invoiceTypeOptions"
                    v-model="pickerInvoiceTypeValue"
                    @confirm="onInvoiceTypeConfirm"
                    @cancel="showInvoiceTypePicker = false"
                />
            </u-popup>
            <up-action-sheet
                :show="showInvoiceTypePicker"
                :actions="invoiceTypeActionList"
                title="选择发票类型"
                @select="onInvoiceTypeSelect"
                @close="showInvoiceTypePicker = false"
            />
            <!-- äº§å“ä¿¡æ¯ -->
            <view class="product-section">
                <view class="section-header">
                    <text class="section-title">产品信息</text>
                    <u-button type="primary" size="small" @click="addProduct" class="add-btn" v-if="operationType !== 'view'">
                        <u-icon name="plus" size="14" />
                        æ–°å¢ž
                    </u-button>
                    <view>
                        <text class="section-title">产品信息</text>
                    </view>
                    <view>
                        <up-button type="primary" size="small" @click="addProduct" class="add-btn" v-if="operationType !== 'view'">
                            æ–°å¢ž
                        </up-button>
                    </view>
                </view>
                <view class="product-card" v-for="(product, idx) in productData" :key="idx">
                    <!-- äº§å“ç±» -->
                    <view class="product-header">
                        <view class="product-title">
                            <u-icon name="file-text" color="#2979ff" size="15" />
                            <view class="document-icon">
                                <up-icon name="file-text" size="16" color="#ffffff"></up-icon>
                            </view>
                            <text class="product-productCategory">产品 {{ idx + 1 }}</text>
                        </view>
                        <!-- æ“ä½œæŒ‰é’® -->
                        <view class="product-actions" v-if="operationType !== 'view'">
                            <u-button type="error" size="mini" @click="removeProduct(idx)" class="del-btn">
                                <u-icon name="trash" size="12" />
                            <up-button type="error" size="mini" @click="removeProduct(idx)" class="del-btn">
                                åˆ é™¤
                            </u-button>
                            </up-button>
                        </view>
                    </view>
                    
                    <!-- äº§å“ä¿¡æ¯è¡¨å• -->
                    <view class="product-form">
                        <!-- äº§å“å¤§ç±» -->
                        <u-form-item
                        <up-form-item
                            label="产品大类"
                            prop="productCategory"
                            required
                            border-bottom
                            :rules="productRules"
                        >
                            <u-input
                            <up-input
                                v-model="product.productCategory"
                                readonly
                                placeholder="请选择"
                                @click="openCategoryPicker(idx)"
                            />
                        </u-form-item>
                            <template #right>
                                <up-icon
                                    name="arrow-right"
                                    @click="showCategoryPicker = true"
                                ></up-icon>
                            </template>
                        </up-form-item>
                        
                        <!-- è§„格型号 -->
                        <u-form-item
                        <up-form-item
                            label="规格型号"
                            prop="specificationModel"
                            required
                            border-bottom
                            :rules="productRules"
                        >
                            <u-input
                            <up-input
                                v-model="product.specificationModel"
                                readonly
                                placeholder="请选择"
                                @click="openSpecificationPicker(idx)"
                            />
                        </u-form-item>
                            <template #right>
                                <up-icon
                                    name="arrow-right"
                                    @click="showSpecificationPicker = true"
                                ></up-icon>
                            </template>
                        </up-form-item>
                        
                        <!-- å•位 -->
                        <u-form-item
                        <up-form-item
                            label="单位"
                            prop="unit"
                            required
                            border-bottom
                            :rules="productRules"
                        >
                            <u-input
                            <up-input
                                v-model="product.unit"
                                placeholder="请输入"
                            />
                        </u-form-item>
                        </up-form-item>
                        
                        <!-- ç¨Žçއ -->
                        <u-form-item
                        <up-form-item
                            label="税率(%)"
                            prop="taxRate"
                            required
                            border-bottom
                            :rules="productRules"
                        >
                            <u-input
                            <up-input
                                v-model="product.taxRate"
                                readonly
                                placeholder="请选择"
                                @click="openTaxRatePicker(idx)"
                            />
                        </u-form-item>
                            <template #right>
                                <up-icon
                                    name="arrow-right"
                                    @click="showTaxRatePicker = true"
                                ></up-icon>
                            </template>
                        </up-form-item>
                        
                        <!-- å«ç¨Žå•ä»· -->
                        <u-form-item
                        <up-form-item
                            label="含税单价(元)"
                            prop="taxInclusiveUnitPrice"
                            required
                            border-bottom
                            :rules="productRules"
                        >
                            <u-input
                            <up-input
                                v-model="product.taxInclusiveUnitPrice"
                                type="number"
                                placeholder="请输入"
                                @blur="formatTaxPrice(idx)"
                            />
                        </u-form-item>
                        </up-form-item>
                        
                        <!-- æ•°é‡ -->
                        <u-form-item
                        <up-form-item
                            label="数量"
                            prop="quantity"
                            required
                            border-bottom
                            :rules="productRules"
                        >
                            <u-input
                            <up-input
                                v-model="product.quantity"
                                type="number"
                                placeholder="请输入"
                                @blur="formatAmount(idx)"
                            />
                        </u-form-item>
                        </up-form-item>
                        
                        <!-- å«ç¨Žæ€»ä»· -->
                        <u-form-item
                        <up-form-item
                            label="含税总价(元)"
                            prop="taxInclusiveTotalPrice"
                            required
                            border-bottom
                            :rules="productRules"
                        >
                            <u-input
                            <up-input
                                v-model="product.taxInclusiveTotalPrice"
                                type="number"
                                placeholder="请输入"
                                @blur="formatTaxTotal(idx)"
                            />
                        </u-form-item>
                        </up-form-item>
                        
                        <!-- ä¸å«ç¨Žæ€»ä»· -->
                        <u-form-item
                        <up-form-item
                            label="不含税总价(元)"
                            prop="taxExclusiveTotalPrice"
                            required
                            border-bottom
                            :rules="productRules"
                        >
                            <u-input
                            <up-input
                                v-model="product.taxExclusiveTotalPrice"
                                type="number"
                                placeholder="请输入"
                                @blur="formatNoTaxTotal(idx)"
                            />
                        </u-form-item>
                        </up-form-item>
                        
                        <!-- å‘票类型 -->
                        <u-form-item
                        <up-form-item
                            label="发票类型"
                            prop="invoiceType"
                            required
                            border-bottom
                            :rules="productRules"
                        >
                            <u-input
                            <up-input
                                v-model="product.invoiceType"
                                readonly
                                placeholder="请选择"
                                @click="openInvoiceTypePicker(idx)"
                            />
                        </u-form-item>
                            <template #right>
                                <up-icon
                                    name="arrow-right"
                                    @click="showInvoiceTypePicker = true"
                                ></up-icon>
                            </template>
                        </up-form-item>
                    </view>
                </view>
            </view>
        </u-form>
        </up-form>
        
        <!-- ä½¿ç”¨å…¬å…±åº•部按钮组件 -->
        <FooterButtons
@@ -322,8 +357,9 @@
</template>
<script setup>
import {onMounted, ref} from 'vue';
import {onMounted, ref, computed} from 'vue';
import {userListNoPage} from "@/api/system/user";
import { formatDateToYMD } from '@/utils/ruoyi'
import {
    addOrUpdateSalesLedger,
    addOrUpdateSalesLedgerProduct,
@@ -340,6 +376,7 @@
// èŽ·å–é¡µé¢å‚æ•°
const operationType = ref('');
const editData = ref(null);
const formRef = ref(null);
const userStore = useUserStore()
const form = ref({
@@ -355,14 +392,80 @@
    entryPersonName: '',
    entryDate: '',
});
const pickerValue = ref(['']);
const pickerDateValue = ref([]);
const showPicker = ref(false);
const showDatePicker = ref(false);
const pickerCustomerValue = ref(['']);
const pickerDateValue = ref(Date.now());
const showCustomerPicker = ref(false);
const userList = ref([]);
const customerOption = ref([]);
const userActionList = computed(() => {
    return userList.value.map(user => ({
        name: user.text,
        value: user.value
    }))
})
const formatter = (type, value) => {
    if (type === 'year') {
        return `${value}`;
    }
    if (type === 'month') {
        return `${value}`;
    }
    if (type === 'day') {
        return `${value}`;
    }
    return value;
};
const customerActionList = computed(() => {
    return customerOption.value.map(customer => ({
        name: customer.text,
        value: customer.value
    }))
})
// æ—¥æœŸé€‰æ‹©åˆ—表已移除,改用 up-datetime-picker
// äº§å“å¤§ç±»é€‰æ‹©åˆ—表
const categoryActionList = computed(() => {
    const flattenCategories = (categories, result = []) => {
        categories.forEach(category => {
            result.push({
                name: category.label,
                value: category.id
            })
            if (category.children && category.children.length > 0) {
                flattenCategories(category.children, result)
            }
        })
        return result
    }
    return flattenCategories(productOptions.value)
})
// è§„格型号选择列表
const specificationActionList = computed(() => {
    return modelOptions.value.map(model => ({
        name: model.text,
        value: model.value,
        unit: model.unit
    }))
})
// ç¨ŽçŽ‡é€‰æ‹©åˆ—è¡¨
const taxRateActionList = computed(() => {
    return taxRateOptions.value.map(rate => ({
        name: rate.text,
        value: rate.value
    }))
})
// å‘票类型选择列表
const invoiceTypeActionList = computed(() => {
    return invoiceTypeOptions.value.map(type => ({
        name: type.text,
        value: type.value
    }))
})
const productData = ref([]);
// é€‰æ‹©å™¨ç›¸å…³å˜é‡
@@ -370,16 +473,9 @@
const showSpecificationPicker = ref(false);
const showTaxRatePicker = ref(false);
const showInvoiceTypePicker = ref(false);
const pickerCategoryValue = ref(['']);
const pickerSpecificationValue = ref(['']);
const pickerTaxRateValue = ref(['']);
const pickerInvoiceTypeValue = ref(['']);
// é€‰æ‹©å™¨æ˜¾ç¤ºçŠ¶æ€å˜é‡å·²åœ¨ä¸Šé¢å®šä¹‰
// ä¸´æ—¶å­˜å‚¨é€‰æ‹©å™¨é€‰ä¸­çš„值
const tempSalesmanValue = ref('');
const tempCustomerValue = ref('');
const selectedSalesman = ref(null);
const selectedCustomer = ref(null);
// ä¸´æ—¶å˜é‡å·²ä¸å†éœ€è¦
const currentProductIndex = ref(0);
// é€‰é¡¹æ•°æ®
@@ -405,6 +501,60 @@
  { text: '增专票', value: '增专票' },
]);
// è¡¨å•校验规则
const rules = {
    salesman: [
        { required: true, message: '请选择业务员', trigger: 'blur' }
    ],
    customerContractNo: [
        { required: true, message: '请输入客户合同号', trigger: 'blur' }
    ],
    customerName: [
        { required: true, message: '请选择客户名称', trigger: 'blur' }
    ],
    projectName: [
        { required: true, message: '请输入项目名称', trigger: 'blur' }
    ],
    executionDate: [
        { required: true, message: '请选择签订日期', trigger: 'blur' }
    ]
};
// äº§å“ä¿¡æ¯æ ¡éªŒè§„则
const productRules = {
    productCategory: [
        { required: true, message: '请选择产品大类', trigger: 'blur' }
    ],
    specificationModel: [
        { required: true, message: '请选择规格型号', trigger: 'blur' }
    ],
    unit: [
        { required: true, message: '请输入单位', trigger: 'blur' }
    ],
    taxRate: [
        { required: true, message: '请选择税率', trigger: 'blur' }
    ],
    taxInclusiveUnitPrice: [
        { required: true, message: '请输入含税单价', trigger: 'blur' },
        { type: 'number', min: 0, message: '含税单价必须大于0', trigger: 'blur' }
    ],
    quantity: [
        { required: true, message: '请输入数量', trigger: 'blur' },
        { type: 'number', min: 0, message: '数量必须大于0', trigger: 'blur' }
    ],
    taxInclusiveTotalPrice: [
        { required: true, message: '请输入含税总价', trigger: 'blur' },
        { type: 'number', min: 0, message: '含税总价必须大于0', trigger: 'blur' }
    ],
    taxExclusiveTotalPrice: [
        { required: true, message: '请输入不含税总价', trigger: 'blur' },
        { type: 'number', min: 0, message: '不含税总价必须大于0', trigger: 'blur' }
    ],
    invoiceType: [
        { required: true, message: '请选择发票类型', trigger: 'blur' }
    ]
};
const addProduct = () => {
    if (productData.value === null) {
        productData.value = []
@@ -422,65 +572,25 @@
    invoiceType: ''
  });
};
// ä¸šåŠ¡å‘˜é€‰æ‹©å™¨å˜åŒ–äº‹ä»¶
const onPickerChange = ({ selectedValues, selectedOptions }) => {
    selectedSalesman.value = selectedOptions[0];
    tempSalesmanValue.value = {
        text: selectedOptions[0]?.text,
        value: selectedOptions[0]?.value
    };
};
// ä¸šåŠ¡å‘˜é€‰æ‹©äº‹ä»¶
const onSalesmanSelect = (item) => {
    form.value.salesman = item.name
}
// ç¡®è®¤é€‰æ‹©ä¸šåŠ¡å‘˜
const confirmSalesman = () => {
    if (selectedSalesman.value) {
        form.value.salesman = selectedSalesman.value.text;
        pickerValue.value = [selectedSalesman.value.value];
    }
    showPicker.value = false;
};
// å®¢æˆ·é€‰æ‹©å™¨å˜åŒ–事件
const onCustomerPickerChange = ({ selectedValues, selectedOptions }) => {
    selectedCustomer.value = selectedOptions[0];
    tempCustomerValue.value = {
        text: selectedOptions[0]?.text,
        value: selectedOptions[0]?.value
    };
};
// ç¡®è®¤é€‰æ‹©å®¢æˆ·
const confirmCustomer = () => {
    if (selectedCustomer.value) {
        form.value.customerName = selectedCustomer.value.text;
        form.value.customerId = selectedCustomer.value.value;
        pickerCustomerValue.value = [selectedCustomer.value.value];
    }
    showCustomerPicker.value = false;
};
// ä¿®æ”¹åŽŸæœ‰çš„ç¡®è®¤æ–¹æ³•ï¼ˆä¿æŒå…¼å®¹æ€§ï¼‰
const onConfirm = ({ selectedValues, selectedOptions }) => {
    if (selectedOptions && selectedOptions[0]) {
        form.value.salesman = selectedOptions[0].text;
        pickerValue.value = [selectedValues[0]];
    }
    showPicker.value = false;
};
const onCustomerConfirm = ({ selectedValues, selectedOptions }) => {
    if (selectedOptions && selectedOptions[0]) {
        form.value.customerName = selectedOptions[0].text;
        form.value.customerId = selectedOptions[0].value;
        pickerCustomerValue.value = [selectedValues[0]];
    }
    showCustomerPicker.value = false;
};
const onDateConfirm = ({ selectedValues }) => {
    form.value.executionDate = selectedValues.join('-');
    pickerDateValue.value = selectedValues;
// æ—¥æœŸç¡®è®¤äº‹ä»¶
const onDateConfirm = (e) => {
    form.value.executionDate = formatDateToYMD(e.value)
    pickerDateValue.value = formatDateToYMD(e.value)
    showDatePicker.value = false;
};
}
// å®¢æˆ·é€‰æ‹©äº‹ä»¶
const onCustomerSelect = (item) => {
    form.value.customerName = item.name
    form.value.customerId = item.value
}
// åŽŸæœ‰çš„ç¡®è®¤æ–¹æ³•å·²è¢«æ–°çš„action-sheet选择方法替代
const removeProduct = (idx) => {
    productData.value.splice(idx, 1);
};
@@ -524,7 +634,6 @@
        selectedCategoryNode.value = null;
        productData.value[currentProductIndex.value].specificationModel = ''
        productData.value[currentProductIndex.value].productModelId = ''
        productData.value[currentProductIndex.value].pickerSpecificationValue = ['']
        getModels(id)
    }
    showCategoryPicker.value = false;
@@ -539,39 +648,27 @@
        }));
    });
};
// é€‰æ‹©è§„格型号
const onSpecificationConfirm = ({ selectedValues, selectedOptions }) => {
    productData.value[currentProductIndex.value].specificationModel = selectedOptions[0]?.text;
    productData.value[currentProductIndex.value].productModelId = selectedOptions[0]?.value;
    productData.value[currentProductIndex.value].unit = selectedOptions[0]?.unit;
  pickerSpecificationValue.value = [selectedValues[0]];
  showSpecificationPicker.value = false;
};
// é€‰æ‹©ç¨Žçއ
const onTaxRateConfirm = ({ selectedValues, selectedOptions }) => {
    productData.value[currentProductIndex.value].taxRate = selectedOptions[0]?.value;
  pickerTaxRateValue.value = [selectedValues[0]];
  showTaxRatePicker.value = false;
    // if (isCalculating.value) return;
    const inclusiveTotalPrice = parseFloat(productData.value[currentProductIndex.value].taxInclusiveTotalPrice);
    const taxRate = parseFloat(productData.value[currentProductIndex.value].taxRate);
    if (!inclusiveTotalPrice || !taxRate) {
        return;
// è§„格型号选择事件
const onSpecificationSelect = (item) => {
    productData.value[currentProductIndex.value].specificationModel = item.name
    productData.value[currentProductIndex.value].productModelId = item.value
    productData.value[currentProductIndex.value].unit = item.unit
}
// ç¨ŽçŽ‡é€‰æ‹©äº‹ä»¶
const onTaxRateSelect = (item) => {
    productData.value[currentProductIndex.value].taxRate = item.value
    // é‡æ–°è®¡ç®—不含税总价
    const inclusiveTotalPrice = parseFloat(productData.value[currentProductIndex.value].taxInclusiveTotalPrice)
    const taxRate = parseFloat(item.value)
    if (inclusiveTotalPrice && taxRate) {
        productData.value[currentProductIndex.value].taxExclusiveTotalPrice =
            calculateTaxExclusiveTotalPrice(inclusiveTotalPrice, taxRate)
    }
    // isCalculating.value = true;
    // è®¡ç®—不含税总价
    productData.value[currentProductIndex.value].taxExclusiveTotalPrice =
        calculateTaxExclusiveTotalPrice(
            inclusiveTotalPrice,
            taxRate
        );
    // isCalculating.value = false;
};
const onInvoiceTypeConfirm = ({ selectedValues, selectedOptions }) => {
    productData.value[currentProductIndex.value].invoiceType = selectedOptions[0]?.text;
  pickerInvoiceTypeValue.value = [selectedValues[0]];
  showInvoiceTypePicker.value = false;
// å‘票类型选择事件
const onInvoiceTypeSelect = (item) => {
    productData.value[currentProductIndex.value].invoiceType = item.name
};
// æ ¼å¼åŒ–函数 - å›ºå®šä¸¤ä½å°æ•°
@@ -698,16 +795,36 @@
    uni.removeStorageSync('editData');
    uni.navigateBack();
};
const onSubmit = () => {
    if (productData.value !== null && productData.value.length > 0) {
        form.value.productData = JSON.parse(JSON.stringify(productData.value));
    } else {
const onSubmit = async () => {
    // é¦–先校验基本表单
    const formValid = await formRef.value.validate().catch(() => false);
    if (!formValid) {
        return;
    }
    // æ ¡éªŒäº§å“ä¿¡æ¯
    if (!productData.value || productData.value.length === 0) {
        uni.showToast({
            title: '请添加产品信息',
            icon: 'none'
        });
        return
        return;
    }
    // æ£€æŸ¥æ¯ä¸ªäº§å“æ˜¯å¦å¡«å†™å®Œæ•´
    for (let i = 0; i < productData.value.length; i++) {
        const errors = validateProduct(productData.value[i], i);
        if (errors.length > 0) {
            uni.showToast({
                title: errors[0],
                icon: 'none'
            });
            return;
        }
    }
    // è¡¨å•校验通过,提交数据
    form.value.productData = JSON.parse(JSON.stringify(productData.value));
    form.value.type = 1;
    addOrUpdateSalesLedger(form.value).then((res) => {
        uni.showToast({
@@ -726,7 +843,8 @@
    const month = String(today.getMonth() + 1).padStart(2, '0')
    const day = String(today.getDate()).padStart(2, '0')
    form.value.entryDate = `${year}-${month}-${day}`
    pickerDateValue.value = [year.toString(), month.toString(), day.toString()]
    // è®¾ç½®æ—¥æœŸé€‰æ‹©å™¨é»˜è®¤å€¼ä¸ºä»Šå¤©
    pickerDateValue.value = `${year}-${month}-${day}`
}
// å¡«å……表单数据(编辑模式)
const fillFormData = () => {
@@ -748,47 +866,30 @@
  form.value.entryDate = editData.value.entryDate || '';
  form.value.id = editData.value.id || '';
  form.value.customerId = editData.value.customerId || '';
  // è®¾ç½®ä¸šåŠ¡å‘˜é€‰æ‹©å™¨çš„å€¼
  if (editData.value.salesman) {
    const salesmanIndex = userList.value.findIndex(user => user.text === editData.value.salesman);
    if (salesmanIndex !== -1) {
      pickerValue.value = [userList.value[salesmanIndex].value];
    }
  }
  // è®¾ç½®å®¢æˆ·é€‰æ‹©å™¨çš„值
  if (editData.value.customerName) {
    const customerIndex = customerOption.value.findIndex(customer => customer.text === editData.value.customerName);
    if (customerIndex !== -1) {
      pickerCustomerValue.value = [customerOption.value[customerIndex].value]
    }
  }
  
  // è®¾ç½®æ—¥æœŸé€‰æ‹©å™¨çš„值
  if (editData.value.executionDate) {
        pickerDateValue.value = editData.value.executionDate.split('-').map(num => parseInt(num, 10))
        console.log(pickerDateValue.value)
        pickerDateValue.value = editData.value.executionDate
    }
};
const getUserList = () => {
    userListNoPage().then((res) => {
        // ç¡®ä¿æ•°æ®æ ¼å¼æ­£ç¡®
        userList.value = [res.data.map(user => ({
        // ç§»é™¤å¤šä½™çš„æ•°ç»„包装
        userList.value = res.data.map(user => ({
            text: user.nickName,
            value: user.nickName
        }))];
        }));
    })
}
};
const getCustomerList = () => {
    customerList().then((res) => {
        // ç¡®ä¿æ•°æ®æ ¼å¼æ­£ç¡®
        customerOption.value = [res.map(item => ({
        // ç§»é™¤å¤šä½™çš„æ•°ç»„包装
        customerOption.value = res.map(item => ({
            text: item.customerName,
            value: item.id
        }))];
        }));
    })
}
};
const convertIdToValue = (data) => {
    // å¦‚果传入的不是数组,则返回空数组
    if (!Array.isArray(data)) {
@@ -814,6 +915,41 @@
        productOptions.value = convertIdToValue(res);
    });
};
// å•个产品表单验证函数
const validateProduct = (product, index) => {
    const errors = [];
    if (!product.productCategory) {
        errors.push(`产品${index + 1}:请选择产品大类`);
    }
    if (!product.specificationModel) {
        errors.push(`产品${index + 1}:请选择规格型号`);
    }
    if (!product.unit) {
        errors.push(`产品${index + 1}:请输入单位`);
    }
    if (!product.taxRate) {
        errors.push(`产品${index + 1}:请选择税率`);
    }
    if (!product.taxInclusiveUnitPrice || parseFloat(product.taxInclusiveUnitPrice) <= 0) {
        errors.push(`产品${index + 1}:请输入有效的含税单价`);
    }
    if (!product.quantity || parseFloat(product.quantity) <= 0) {
        errors.push(`产品${index + 1}:请输入有效的数量`);
    }
    if (!product.taxInclusiveTotalPrice || parseFloat(product.taxInclusiveTotalPrice) <= 0) {
        errors.push(`产品${index + 1}:请输入有效的含税总价`);
    }
    if (!product.taxExclusiveTotalPrice || parseFloat(product.taxExclusiveTotalPrice) <= 0) {
        errors.push(`产品${index + 1}:请输入有效的不含税总价`);
    }
    if (!product.invoiceType) {
        errors.push(`产品${index + 1}:请选择发票类型`);
    }
    return errors;
};
onMounted(() => {
    // èŽ·å–é¡µé¢å‚æ•°
    operationType.value = uni.getStorageSync('operationType') || '';
@@ -848,226 +984,6 @@
});
</script>
<style scoped lang="scss">
.account-detail {
  min-height: 100vh;
  background: #f8f9fa;
  padding-bottom: 5rem;
}
.header {
  display: flex;
  align-items: center;
  background: #fff;
  padding: 1rem 1.25rem;
  border-bottom: 0.0625rem solid #f0f0f0;
  position: sticky;
  top: 0;
  z-index: 100;
    /* å…¼å®¹ iOS åˆ˜æµ·/灵动岛安全区 */
    padding-top: env(safe-area-inset-top);
}
.title {
  flex: 1;
  text-align: center;
  font-size: 1.125rem;
  font-weight: 600;
  color: #333;
}
.form-section {
    margin-top: 1rem;
}
.van-field {
    height: 3.4rem;
}
.van-cell {
    align-items: center;
}
.product-section {
  background: #fff;
    margin-top: 1rem;
  padding: 1rem;
  box-shadow: 0 0.125rem 0.5rem rgba(0,0,0,0.04);
}
.section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1rem;
}
.section-title {
  font-size: 1rem;
  font-weight: 600;
  color: #333;
}
.product-card {
    background: #FFFFFF;
    box-shadow: 0 0 1.25rem 0 rgba(0,57,117,0.08);
    border-radius: 0.5rem 0.5rem 0.5rem 0.5rem;
  padding: 1rem 0.5rem 0 0.5rem;
  position: relative;
}
.product-header {
  display: flex;
  align-items: center;
    justify-content: space-between;
  padding: 0 0.5rem 0.75rem 0.5rem;
  border-bottom: 0.0625rem solid #e8e8e8;
}
.product-productCategory {
  margin-left: 0.5rem;
  font-size: 0.875rem;
  font-weight: 500;
  color: #333;
}
.info-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.75rem;
  margin-bottom: 1rem;
}
.info-item {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.info-label {
  font-size: 0.75rem;
  color: #666;
  font-weight: 400;
}
.info-value {
  font-size: 0.875rem;
  color: #333;
  font-weight: 500;
}
.info-value.highlight {
  color: #2979ff;
  font-weight: 600;
}
.product-form {
  margin-bottom: 1rem;
}
.popup-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 1rem;
    background: #fff;
    position: sticky;
    top: 0;
    z-index: 10;
}
.cancelButton {
    color: #969799
}
.confirmButton {
    color: #1989FA
}
.u-tree {
    height: 13rem;
}
/* ç§»é™¤ input è¾¹æ¡†çš„æ ·å¼ */
:deep(.u-input) {
  border: none !important;
  box-shadow: none !important;
  background: transparent !important;
}
:deep(.u-input__content) {
  border: none !important;
  box-shadow: none !important;
  background: transparent !important;
}
:deep(.u-input__content__field-wrapper) {
  border: none !important;
  box-shadow: none !important;
  background: transparent !important;
}
:deep(.u-input__content__field-wrapper__field) {
  border: none !important;
  box-shadow: none !important;
  background: transparent !important;
  outline: none !important;
}
/* ç§»é™¤ textarea è¾¹æ¡†çš„æ ·å¼ */
:deep(.u-textarea) {
  border: none !important;
  box-shadow: none !important;
  background: transparent !important;
}
:deep(.u-textarea__content) {
  border: none !important;
  box-shadow: none !important;
  background: transparent !important;
}
:deep(.u-textarea__content__field) {
  border: none !important;
  box-shadow: none !important;
  background: transparent !important;
  outline: none !important;
}
/* ç§»é™¤ form-item çš„边框 */
:deep(.u-form-item) {
  border: none !important;
}
:deep(.u-form-item__body) {
  border: none !important;
}
/* ä¿æŒåˆ†å‰²çº¿æ ·å¼ */
:deep(.u-form-item--border-bottom) {
  border-bottom: 1px solid #ebeef5 !important;
}
/* é€‰æ‹©å™¨å¤´éƒ¨æ ·å¼ */
.picker-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 15px 20px;
    background: #fff;
    border-bottom: 1px solid #ebeef5;
}
.picker-cancel {
    color: #909399;
    font-size: 16px;
}
.picker-title {
    color: #303133;
    font-size: 16px;
    font-weight: 500;
}
.picker-confirm {
    color: #2979ff;
    font-size: 16px;
}
<style lang="scss">
@import '@/static/scss/form-common.scss';
</style>
src/pages/template.vue
@@ -1,65 +1,48 @@
<script setup lang="ts">
import tab from "@/plugins/tab";
import list from "./template.config.js";
interface ListItem {
  groupName: string;
  list: FieldItem[];
}
interface FieldItem {
  title: string;
  icon: string;
  path: string;
}
const listData = list as ListItem[];
const getIcon = (path: string) => `../static/uview/demo/${path}.png`;
const openPage = (path: string) => tab.navigateTo(path)
const getGroupTitle = (item: ListItem) => item.groupName;
const getFieldTitle = (item: FieldItem) => item.title;
</script>
<template>
    <view class="wrap">
      <view class="list-wrap">
        <u-cell-group title-bg-color="rgb(243, 244, 246)" :title="getGroupTitle(item)" v-for="(item, index) in list"
          :key="index">
          <u-cell :titleStyle="{ fontWeight: 500 }" @click="openPage(item1.path)" :title="getFieldTitle(item1)"
            v-for="(item1, index1) in item.list" :key="index1">
            <template v-slot:icon>
              <image class="u-cell-icon" :src="getIcon(item1.icon)" mode="widthFix"></image>
            </template>
          </u-cell>
        </u-cell-group>
      </view>
      <u-gap height="70"></u-gap>
      <!-- <u-tabbar :list="vuex_tabbar" :mid-button="true"></u-tabbar> -->
  <view class="wrap">
    <view class="list-wrap">
      <u-cell-group title-bg-color="rgb(243, 244, 246)" :title="getGroupTitle(item)" v-for="(item, index) in listData"
        :key="index">
        <u-cell :titleStyle="{ fontWeight: 500 }" @click="openPage(item1.path)" :title="getFieldTitle(item1)"
          v-for="(item1, index1) in item.list" :key="index1">
          <template v-slot:icon>
            <image class="u-cell-icon" :src="getIcon(item1.icon)" mode="widthFix"></image>
          </template>
        </u-cell>
      </u-cell-group>
    </view>
  </template>
  <script>
  import list from "./template.config.js";
  export default {
    data() {
      return {
        list: list,
        // desc: '收集众多的常用页面和布局,减少开发者的重复工作,让你专注逻辑,事半功倍'
      }
    },
    computed: {
      getIcon() {
        return path => {
          return '../static/uview/demo/' + path + '.png';
          return 'https://cdn.uviewui.com/uview/example/' + path + '.png';
        }
      },
    },
    methods: {
      openPage(path) {
        this.$u.route({
          url: path
        })
      },
      getGroupTitle(item) {
        return item.groupName
      },
      getFieldTitle(item) {
        return item.title
      }
    }
  }
  </script>
  <style>
  /* page {
          background-color: rgb(240, 242, 244);
      } */
  </style>
  <style lang="scss" scoped>
  .u-cell-icon {
    width: 36rpx;
    height: 36rpx;
    margin-right: 8rpx;
  }
  </style>
    <u-gap height="70"></u-gap>
  </view>
</template>
<style lang="scss" scoped>
page {
  background-color: rgb(240, 242, 244);
}
.u-cell-icon {
  width: 36rpx;
  height: 36rpx;
  margin-right: 8rpx;
}
</style>
src/pages/work.vue
@@ -75,110 +75,105 @@
</template>
<script setup>
    import { ref } from "vue";
  import modal from "@/plugins/modal"
    const current=ref(0);
    const swiperDotIndex=ref(0);
    const data=ref([{
            image: '/static/images/banner/banner01.jpg'
          },
          {
            image: '/static/images/banner/banner02.jpg'
          },
          {
            image: '/static/images/banner/banner03.jpg'
          }
        ]);
    function clickBannerItem(item) {
      console.info(item)
    };
    function changeSwiper(e) {
      current.value = e.detail.current
    }
    function changeGrid(e) {
      modal.showToast({
        title: '模块建设中',
        mask: false,
          icon:'loading',
        duration: 1000
      });
    }
import { ref } from "vue";
import modal from "@/plugins/modal"
const current = ref(0);
const swiperDotIndex = ref(0);
const data = ref([
  { image: '/static/images/banner/banner01.jpg' },
  { image: '/static/images/banner/banner02.jpg' },
  { image: '/static/images/banner/banner03.jpg' }
]);
function clickBannerItem(item) {
  console.info(item)
};
function changeSwiper(e) {
  current.value = e.detail.current
}
function changeGrid(e) {
  modal.showToast({
    title: '模块建设中',
    mask: false,
    icon: 'loading',
    duration: 1000
  });
}
</script>
<style lang="scss">
/* #ifndef APP-NVUE */
page {
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  background-color: #fff;
  min-height: 100%;
  height: auto;
}
view {
  font-size: 14px;
  line-height: inherit;
}
/* #endif */
.text {
  text-align: center;
  font-size: 26rpx;
  margin-top: 10rpx;
}
.grid-item-box {
  flex: 1;
  /* #ifndef APP-NVUE */
  page {
    display: flex;
    flex-direction: column;
    box-sizing: border-box;
    background-color: #fff;
    min-height: 100%;
    height: auto;
  }
  view {
    font-size: 14px;
    line-height: inherit;
  }
  display: flex;
  /* #endif */
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 15px 0;
}
  .text {
    text-align: center;
    font-size: 26rpx;
    margin-top: 10rpx;
  }
.uni-margin-wrap {
  width: 690rpx;
  width: 100%;
  ;
}
  .grid-item-box {
    flex: 1;
.swiper {
  height: 300rpx;
}
.swiper-box {
  height: 150px;
}
.swiper-item {
  /* #ifndef APP-NVUE */
  display: flex;
  /* #endif */
  flex-direction: column;
  justify-content: center;
  align-items: center;
  color: #fff;
  height: 300rpx;
  line-height: 300rpx;
}
@media screen and (min-width: 500px) {
  .uni-swiper-dot-box {
    width: 400px;
    /* #ifndef APP-NVUE */
    display: flex;
    margin: 0 auto;
    /* #endif */
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 15px 0;
    margin-top: 8px;
  }
  .uni-margin-wrap {
    width: 690rpx;
  .image {
    width: 100%;
    ;
  }
  .swiper {
    height: 300rpx;
  }
  .swiper-box {
    height: 150px;
  }
  .swiper-item {
    /* #ifndef APP-NVUE */
    display: flex;
    /* #endif */
    flex-direction: column;
    justify-content: center;
    align-items: center;
    color: #fff;
    height: 300rpx;
    line-height: 300rpx;
  }
  @media screen and (min-width: 500px) {
    .uni-swiper-dot-box {
      width: 400px;
      /* #ifndef APP-NVUE */
      margin: 0 auto;
      /* #endif */
      margin-top: 8px;
    }
    .image {
      width: 100%;
    }
  }
}
</style>
src/pages_mine/pages/company/index.vue
@@ -1,10 +1,10 @@
<template>
  <view class="container">
    <view class="card">
      <van-cell-group>
        <van-cell icon="cluster-o" title="所属公司" :value="userStore.currentFactoryName" />
        <van-cell icon="manager-o" title="岗位" :value="postGroup" />
      </van-cell-group>
      <u-cell-group>
        <u-cell icon="list" title="所属公司" :value="userStore.currentFactoryName" />
        <u-cell icon="bag" title="岗位" :value="postGroup" />
      </u-cell-group>
    </view>
    <!-- <u-button @click="register()">绑定微信</u-button> -->
src/pages_mine/pages/contract/index.vue
@@ -1,12 +1,12 @@
<template>
  <view class="container">
    <view class="card">
      <van-cell-group>
        <van-cell icon="underway-o" title="合同开始时间" :value="user.contractStartTime" />
        <van-cell icon="underway-o" title="合同结束时间" :value="user.contractEndTime" />
        <van-cell icon="medal-o" title="岗位" :value="user.postJob" />
        <van-cell icon="label-o" title="第一学历" :value="user.firstStudy" />
      </van-cell-group>
      <u-cell-group>
        <u-cell icon="calendar" title="合同开始时间" :value="user.contractStartTime" />
        <u-cell icon="calendar" title="合同结束时间" :value="user.contractEndTime" />
        <u-cell icon="bag" title="职位" :value="user.postJob" />
        <u-cell icon="integral" title="第一学历" :value="user.firstStudy" />
      </u-cell-group>
    </view>
    <!-- <u-button @click="register()">绑定微信</u-button> -->
src/pages_mine/pages/info/index.vue
@@ -1,14 +1,14 @@
<template>
  <view class="container">
    <view class="card">
      <van-cell-group>
        <van-cell icon="user" title="昵称" :value="user.nickName" />
        <van-cell icon="phone" title="手机号码" :value="user.phonenumber" />
        <van-cell icon="invitation" title="邮箱" :value="user.email" />
        <van-cell icon="medal" title="岗位" :value="postGroup" />
        <van-cell icon="friends" title="角色" :value="roleGroup" />
        <van-cell icon="notes" title="创建日期" :value="user.createTime" />
      </van-cell-group>
      <u-cell-group>
        <u-cell icon="account" title="昵称" :value="user.nickName" />
        <u-cell icon="phone" title="手机号码" :value="user.phonenumber" />
        <u-cell icon="email" title="邮箱" :value="user.email" />
        <u-cell icon="order" title="岗位" :value="postGroup" />
        <u-cell icon="star" title="角色" :value="roleGroup" />
        <u-cell icon="calendar" title="创建日期" :value="user.createTime" />
      </u-cell-group>
    </view>
    <!-- <u-button @click="register()">绑定微信</u-button> -->
src/pages_qiun/components/data-progress/data-progress.vue
@@ -6,7 +6,7 @@
                <view class="number">{{item.now?item.now+"/":""}}{{item.expect}} [{{item.value}}%]</view>
                <progress :percent="item.value" backgroundColor="#C9C9C9"
                :border-radius="borderRadius?borderRadius+'rpx':'0px'"
                :color="time"
                active="true"
                stroke-width="16" />
            </view>
        </view>
src/pages_qiun/pages/main/index.vue
@@ -21,7 +21,7 @@
            </view>
            <!-- å…¬å¸åŒºåŸŸä¸‹æ‹‰åˆ—表 -->
            <drop-down ref="companyDrop" @tap="changDrop(2)" @changeItem="changeLocation" :list="locationArray"
                :contentTop="top" contentRight="10" :selectWidth="showCalendar ? '200rpx' : '300rpx'" listWidth="75%">
                :contentTop="top" contentRight="10" :selectWidth="showCalendar ? '200rpx' : '400rpx'" listWidth="75%">
            </drop-down>
        </view>
        <uni-calendar ref="calendar" :insert="false" :start-date="startDate" :end-date="endDate" :clearDate="false"
@@ -245,6 +245,8 @@
    }
    .head {
        display: flex;
        justify-content: space-between;
        padding: 0 16rpx 14rpx 16rpx;
        color: #fff;
        background-color: #40A2ED;
src/pages_template/pages/coupon/components/jingdong-coupon.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,136 @@
<template>
    <view class="jingdong">
        <view class="left">
            <view class="sum">
                ï¿¥
                <text class="num">100</text>
            </view>
            <view class="type">满149元可用</view>
        </view>
        <view class="right">
            <view class="top">
                <view class="title">
                    <text class="tag">限品类东券</text>
                    <text>仅可购买个人护理部分商品</text>
                </view>
                <view class="bottom">
                    <view class="date u-line-1">2020.01.01-2020.01.31</view>
                    <view class="immediate-use">立即使用</view>
                </view>
            </view>
            <view class="tips">
                <view class="explain">
                    <u-icon name="zhuanfa" class="transpond" :size="24"></u-icon>
                    <text>可赠送</text>
                </view>
            </view>
        </view>
    </view>
</template>
<style scoped lang="scss">
.jingdong {
    margin-top: 40rpx;
    width: 700rpx;
    height: auto;
    background-color: #ffffff;
    display: flex;
    .left {
        padding: 0 30rpx;
        background-color: rgb(95, 148, 224); //rgb(94, 152, 225);
        text-align: center;
        font-size: 28rpx;
        color: #ffffff;
        .sum {
            margin-top: 50rpx;
            font-weight: bold;
            font-size: 32rpx;
            .num {
                font-size: 80rpx;
            }
        }
        .type {
            margin-bottom: 50rpx;
            font-size: 24rpx;
        }
    }
    .right {
        padding: 20rpx 20rpx 0;
        font-size: 28rpx;
        .top {
            border-bottom: 2rpx dashed $u-border-color;
            .title {
                margin-right: 60rpx;
                line-height: 40rpx;
                .tag {
                    padding: 4rpx 20rpx;
                    background-color: rgb(73, 154, 201);
                    border-radius: 20rpx;
                    color: #ffffff;
                    font-weight: bold;
                    font-size: 24rpx;
                    margin-right: 10rpx;
                }
            }
            .bottom {
                display: flex;
                margin-top: 20rpx;
                align-items: center;
                justify-content: space-between;
                margin-bottom: 10rpx;
                .date {
                    font-size: 20rpx;
                    flex: 1;
                }
                .immediate-use {
                    height: auto;
                    padding: 0 20rpx;
                    font-size: 24rpx;
                    border-radius: 40rpx;
                    line-height: 40rpx;
                    color: rgb(117, 142, 165);
                    border: 2rpx solid rgb(117, 142, 165);
                }
            }
        }
        .tips {
            width: 100%;
            line-height: 50rpx;
            display: flex;
            align-items: center;
            justify-content: space-between;
            font-size: 24rpx;
            .transpond {
                margin-right: 10rpx;
            }
            .explain {
                display: flex;
                align-items: center;
            }
            .particulars {
                width: 30rpx;
                height: 30rpx;
                box-sizing: border-box;
                padding-top: 8rpx;
                border-radius: 50%;
                background-color: $u-info-disabled;
                text-align: center;
            }
        }
    }
}
</style>
src/pages_template/pages/coupon/components/meituan-coupon.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,127 @@
<template>
    <view class="meituan">
        <view class="content">
            <view class="left">
                <view class="sum">
                    ï¿¥<text class="num">8</text>
                </view>
                <view class="type">抵用券</view>
            </view>
            <view class="centre">
                <view class="title">【洗牙】8元无门槛红包</view>
                <view class="valid-date">今日到期</view>
            </view>
            <view class="right">
                <view size="mini" class="immediate-use" :round="true">立即使用</view>
            </view>
        </view>
        <view class="tips">
            <view class="circle-left"></view>
            <view class="circle-right"></view>
            <view class="explain u-line-1">满8.1元可用、限最新版本客户端使用</view>
            <view class="rule">
                <text>使用规则</text>
                <u-icon name="arrow-right" color="" :size="20" @click=""></u-icon>
            </view>
        </view>
    </view>
</template>
<style scoped lang="scss">
.meituan {
    margin: 30rpx auto;
    background-color: #ffffff;
    width: 700rpx;
    // border: 10rpx;
    color: $u-warning;
    font-size: 28rpx;
    .content {
        display: flex;
        align-items: center;
        padding: 80rpx 20rpx;
        border: 10rpx;
        background-color: #fff5f4;
        .left {
            .sum {
                font-size: 32rpx;
                .num {
                    font-size: 60rpx;
                    font-weight: bold;
                }
            }
        }
        .centre {
            margin-left: 40rpx;
            .title {
                font-size: 32rpx;
                font-weight: bold;
                color: $u-main-color;
                margin-bottom: 20rpx;
            }
        }
        .right {
            margin-left: 30rpx;
            .immediate-use {
                padding: 0 20rpx;
                height: 50rpx;
                border-radius: 25rpx;
                line-height: 50rpx;
                background-color: $u-warning !important;
                color: #ffffff !important;
                font-size: 24rpx;
                border: none;
                word-break: keep-all;
            }
        }
    }
    .tips {
        padding: 0 20rpx;
        border: 10rpx;
        background-color: $u-info-light;
        position: relative;
        color: $u-tips-color;
        display: flex;
        justify-content: space-between;
        line-height: 60rpx;
        font-size: 24rpx;
        .circle-left,
        .circle-right {
            position: absolute;
            height: 36rpx;
            width: 18rpx;
            background-color: #f2f2f2;
        }
        .circle-right {
            border-radius: 40rpx 0 0 40rpx;
            right: 0;
            top: -18rpx;
        }
        .circle-left {
            border-radius: 0 40rpx 40rpx 0;
            left: 0;
            top: -18rpx;
        }
        .rule {
            font-size: 24rpx;
            display: flex;
            align-items: center;
            text {
                margin-right: 10rpx;
                flex: 1;
            }
        }
    }
}
</style>
src/pages_template/pages/coupon/components/taobao-coupon.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,140 @@
<script setup lang="ts">
import logo from '@/static/logo.png';
</script>
<template>
    <view class="taobao">
        <view class="title">
            <view class="left">
                <image class="buddha" :src="logo" mode="aspectFill"></image>
                <view class="store">袜子精保护协会</view>
            </view>
            <view class="entrance">进店</view>
        </view>
        <view class="ticket">
            <view class="left">
                <image class="picture" :src="logo" mode="widthFix"></image>
                <view class="introduce">
                    <view class="top">
                        ï¿¥
                        <text class="big">3</text>
                        æ»¡88减3
                    </view>
                    <view class="type">店铺优惠券</view>
                    <view class="date u-line-1">2019.11.28-2020.1.24</view>
                </view>
            </view>
            <view class="right">
                <view class="use immediate-use" :round="true">去使用</view>
            </view>
        </view>
    </view>
</template>
<style scoped lang="scss">
.taobao {
    margin-top: 40rpx;
    width: 700rpx;
    background-color: white;
    padding: 30rpx 20rpx 20rpx;
    border-radius: 20rpx;
    .title {
        display: flex;
        align-items: center;
        justify-content: space-between;
        margin-bottom: 20rpx;
        font-size: 30rpx;
        .left {
            display: flex;
            align-items: center;
        }
        .store {
            font-weight: 500;
        }
        .buddha {
            width: 70rpx;
            height: 70rpx;
            border-radius: 10rpx;
            margin-right: 10rpx;
        }
        .entrance {
            color: $u-info;
            border: solid 2rpx $u-info;
            line-height: 48rpx;
            padding: 0 30rpx;
            background: none;
            border-radius: 15px;
        }
    }
    .ticket {
        display: flex;
        .left {
            width: 70%;
            padding: 30rpx 20rpx;
            background-color: rgb(255, 245, 244);
            border-radius: 20rpx;
            border-right: dashed 2rpx rgb(224, 215, 211);
            display: flex;
            .picture {
                width: 172rpx;
                height: 172rpx;
                border-radius: 20rpx;
            }
            .introduce {
                margin-left: 10rpx;
                .top {
                    color: $u-warning;
                    font-size: 28rpx;
                    .big {
                        font-size: 60rpx;
                        font-weight: bold;
                        margin-right: 10rpx;
                    }
                }
                .type {
                    font-size: 28rpx;
                    color: $u-info-dark;
                }
                .date {
                    margin-top: 10rpx;
                    font-size: 20rpx;
                    color: $u-info-dark;
                }
            }
        }
        .right {
            width: 30%;
            padding: 40rpx 20rpx;
            background-color: rgb(255, 245, 244);
            border-radius: 20rpx;
            display: flex;
            align-items: center;
            .use {
                height: auto;
                padding: 0 20rpx;
                font-size: 24rpx;
                border-radius: 40rpx;
                color: #ffffff !important;
                background-color: $u-warning !important;
                line-height: 40rpx;
                color: rgb(117, 142, 165);
                margin-left: 20rpx;
            }
        }
    }
}
</style>
src/pages_template/pages/coupon/index.vue
@@ -1,92 +1,13 @@
<script setup>
import MeituanCoupon from './components/meituan-coupon.vue';
import JingdongCoupon from './components/jingdong-coupon.vue';
import TaobaoCoupon from './components/taobao-coupon.vue';
</script>
<template>
    <view class="u-wrap">
        <view class="meituan">
            <view class="content">
                <view class="left">
                    <view class="sum">
                        ï¿¥
                        <text class="num">8</text>
                    </view>
                    <view class="type">抵用券</view>
                </view>
                <view class="centre">
                    <view class="title">【洗牙】8元无门槛红包</view>
                    <view class="valid-date">今日到期</view>
                </view>
                <view class="right">
                    <view size="mini" class="immediate-use" :round="true">立即使用</view>
                </view>
            </view>
            <view class="tips">
                <view class="circle-left"></view>
                <view class="circle-right"></view>
                <view class="explain u-line-1">满8.1元可用、限最新版本客户端使用</view>
                <view class="rule" @tap="xxx122">
                    <text>使用规则</text>
                    <u-icon name="arrow-right" color="" :size="20" @click=""></u-icon>
                </view>
            </view>
        </view>
        <view class="jingdong">
            <view class="left">
                <view class="sum">
                    ï¿¥
                    <text class="num">100</text>
                </view>
                <view class="type">满149元可用</view>
            </view>
            <view class="right">
                <view class="top">
                    <view class="title">
                        <text class="tag">限品类东券</text>
                        <text>仅可购买个人护理部分商品</text>
                    </view>
                    <view class="bottom">
                        <view class="date u-line-1">2020.01.01-2020.01.31</view>
                        <view class="immediate-use">立即使用</view>
                    </view>
                </view>
                <view class="tips">
                    <view class="explain">
                        <u-icon name="zhuanfa" class="transpond" :size="24"></u-icon>
                        <text>可赠送</text>
                    </view>
                </view>
            </view>
        </view>
        <view class="taobao">
            <view class="title">
                <view class="left">
                    <image class="buddha"
                        src="https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1975388697,1068670603&fm=26&gp=0.jpg"
                        mode="aspectFill"></image>
                    <view class="store">袜子精保护协会</view>
                </view>
                <view class="entrance">进店</view>
            </view>
            <view class="ticket">
                <view class="left">
                    <image class="picture"
                        src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1578059523488&di=5f592ac19c1b983005d3e85add469756&imgtype=0&src=http%3A%2F%2Fimg010.hc360.cn%2Fg7%2FM00%2F2D%2FB9%2FwKhQs1QfUo6EdeM-AAAAALwk1hM072.jpg"
                        mode="widthFix"></image>
                    <view class="introduce">
                        <view class="top">
                            ï¿¥
                            <text class="big">3</text>
                            æ»¡88减3
                        </view>
                        <view class="type">店铺优惠券</view>
                        <view class="date u-line-1">2019.11.28-2020.1.24</view>
                    </view>
                </view>
                <view class="right">
                    <view class="use immediate-use" :round="true">去使用</view>
                </view>
            </view>
        </view>
        <MeituanCoupon />
        <JingdongCoupon />
        <TaobaoCoupon />
    </view>
</template>
@@ -98,316 +19,5 @@
.u-wrap {
    padding: 24rpx;
}
.meituan {
    margin: 30rpx auto;
    background-color: #ffffff;
    width: 700rpx;
    // border: 10rpx;
    color: $u-warning;
    font-size: 28rpx;
    .content {
        display: flex;
        align-items: center;
        padding: 80rpx 20rpx;
        border: 10rpx;
        background-color: #fff5f4;
        .left {
            .sum {
                font-size: 32rpx;
                .num {
                    font-size: 60rpx;
                    font-weight: bold;
                }
            }
        }
        .centre {
            margin-left: 40rpx;
            .title {
                font-size: 32rpx;
                font-weight: bold;
                color: $u-main-color;
                margin-bottom: 20rpx;
            }
        }
        .right {
            margin-left: 30rpx;
            .immediate-use {
                padding: 0 20rpx;
                height: 50rpx;
                border-radius: 25rpx;
                line-height: 50rpx;
                background-color: $u-warning !important;
                color: #ffffff !important;
                font-size: 24rpx;
                border: none;
                word-break: keep-all;
            }
        }
    }
    .tips {
        padding: 0 20rpx;
        border: 10rpx;
        background-color: $u-info-light;
        position: relative;
        color: $u-tips-color;
        display: flex;
        justify-content: space-between;
        line-height: 60rpx;
        font-size: 24rpx;
        .circle-left,
        .circle-right {
            position: absolute;
            height: 36rpx;
            width: 18rpx;
            background-color: #f2f2f2;
        }
        .circle-right {
            border-radius: 40rpx 0 0 40rpx;
            right: 0;
            top: -18rpx;
        }
        .circle-left {
            border-radius: 0 40rpx 40rpx 0;
            left: 0;
            top: -18rpx;
        }
        .rule {
            font-size: 24rpx;
            display: flex;
            align-items: center;
            text {
                margin-right: 10rpx;
                flex: 1;
            }
        }
    }
}
.jingdong {
    margin-top: 40rpx;
    width: 700rpx;
    height: auto;
    background-color: #ffffff;
    display: flex;
    .left {
        padding: 0 30rpx;
        background-color: rgb(95, 148, 224); //rgb(94, 152, 225);
        text-align: center;
        font-size: 28rpx;
        color: #ffffff;
        .sum {
            margin-top: 50rpx;
            font-weight: bold;
            font-size: 32rpx;
            .num {
                font-size: 80rpx;
            }
        }
        .type {
            margin-bottom: 50rpx;
            font-size: 24rpx;
        }
    }
    .right {
        padding: 20rpx 20rpx 0;
        font-size: 28rpx;
        .top {
            border-bottom: 2rpx dashed $u-border-color;
            .title {
                margin-right: 60rpx;
                line-height: 40rpx;
                .tag {
                    padding: 4rpx 20rpx;
                    background-color: rgb(73, 154, 201);
                    border-radius: 20rpx;
                    color: #ffffff;
                    font-weight: bold;
                    font-size: 24rpx;
                    margin-right: 10rpx;
                }
            }
            .bottom {
                display: flex;
                margin-top: 20rpx;
                align-items: center;
                justify-content: space-between;
                margin-bottom: 10rpx;
                .date {
                    font-size: 20rpx;
                    flex: 1;
                }
                .immediate-use {
                    height: auto;
                    padding: 0 20rpx;
                    font-size: 24rpx;
                    border-radius: 40rpx;
                    line-height: 40rpx;
                    color: rgb(117, 142, 165);
                    border: 2rpx solid rgb(117, 142, 165);
                }
            }
        }
        .tips {
            width: 100%;
            line-height: 50rpx;
            display: flex;
            align-items: center;
            justify-content: space-between;
            font-size: 24rpx;
            .transpond {
                margin-right: 10rpx;
            }
            .explain {
                display: flex;
                align-items: center;
            }
            .particulars {
                width: 30rpx;
                height: 30rpx;
                box-sizing: border-box;
                padding-top: 8rpx;
                border-radius: 50%;
                background-color: $u-info-disabled;
                text-align: center;
            }
        }
    }
}
.taobao {
    margin-top: 40rpx;
    width: 700rpx;
    background-color: white;
    padding: 30rpx 20rpx 20rpx;
    border-radius: 20rpx;
    .title {
        display: flex;
        align-items: center;
        justify-content: space-between;
        margin-bottom: 20rpx;
        font-size: 30rpx;
        .left {
            display: flex;
            align-items: center;
        }
        .store {
            font-weight: 500;
        }
        .buddha {
            width: 70rpx;
            height: 70rpx;
            border-radius: 10rpx;
            margin-right: 10rpx;
        }
        .entrance {
            color: $u-info;
            border: solid 2rpx $u-info;
            line-height: 48rpx;
            padding: 0 30rpx;
            background: none;
            border-radius: 15px;
        }
    }
    .ticket {
        display: flex;
        .left {
            width: 70%;
            padding: 30rpx 20rpx;
            background-color: rgb(255, 245, 244);
            border-radius: 20rpx;
            border-right: dashed 2rpx rgb(224, 215, 211);
            display: flex;
            .picture {
                width: 172rpx;
                height: 172rpx;
                border-radius: 20rpx;
            }
            .introduce {
                margin-left: 10rpx;
                .top {
                    color: $u-warning;
                    font-size: 28rpx;
                    .big {
                        font-size: 60rpx;
                        font-weight: bold;
                        margin-right: 10rpx;
                    }
                }
                .type {
                    font-size: 28rpx;
                    color: $u-info-dark;
                }
                .date {
                    margin-top: 10rpx;
                    font-size: 20rpx;
                    color: $u-info-dark;
                }
            }
        }
        .right {
            width: 30%;
            padding: 40rpx 20rpx;
            background-color: rgb(255, 245, 244);
            border-radius: 20rpx;
            display: flex;
            align-items: center;
            .use {
                height: auto;
                padding: 0 20rpx;
                font-size: 24rpx;
                border-radius: 40rpx;
                color: #ffffff !important;
                background-color: $u-warning !important;
                line-height: 40rpx;
                color: rgb(117, 142, 165);
                margin-left: 20rpx;
            }
        }
    }
}
</style>
src/pages_template/pages/keyboardPay/index.vue
@@ -1,12 +1,8 @@
<script setup>
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
const show = ref(false)
const password = ref('')
onLoad(() => {
})
const onChange = (val) => {
    if (password.value.length < 6) {
@@ -25,10 +21,7 @@
}
const pay = () => {
    uni.showLoading({
        title: '支付中'
    })
    uni.showLoading({ title: '支付中' })
    setTimeout(() => {
        uni.hideLoading()
        show.value = false
src/static/scss/form-common.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,270 @@
/* è¡¨å•公共样式 */
/* è¡¨å•整体样式 - ä½¿ç”¨ :deep ç©¿é€åˆ° uView-plus å†…部类 */
:deep(.u-form) {
  background: transparent !important;
  margin: 0 !important;
  padding: 0 !important;
}
/* è¡¨å•项样式优化 */
:deep(.u-form-item) {
  background: #fff !important;
  padding: 0 22px !important;
  border: none !important;
  position: relative !important;
  border-bottom: 1px solid #f0f0f0 !important;
}
:deep(.u-form-item:last-child) {
  border-bottom: none !important;
}
:deep(.u-form-item__body) {
  padding: 15px 0 !important;
  border: none !important;
  min-height: 50px !important;
  display: flex !important;
  align-items: center !important;
  align-items: flex-start !important;
}
:deep(.u-form-item__label) {
  font-size: 15px !important;
  color: #333 !important;
  font-weight: 400 !important;
  width: 90px !important;
  flex-shrink: 0 !important;
}
:deep(.u-form-item__content) {
  flex: 1 !important;
  display: flex !important;
  justify-content: flex-end !important;
}
/* å½»åº•移除 input è¾¹æ¡†ä¸ŽèƒŒæ™¯ï¼ˆç©¿é€åˆ°å†…部元素)*/
:deep(.u-input),
:deep(.u-input *),
input[type="text"],
input[type="number"] {
  border: none !important;
  box-shadow: none !important;
  background: transparent !important;
  padding: 0 !important;
  margin: 0 !important;
  outline: none !important;
  -webkit-appearance: none !important;
  appearance: none !important;
}
:deep(.u-input input),
:deep(.u-input__content__field-wrapper__field),
:deep(.u-input__input) {
  font-size: 15px !important;
  color: #333 !important;
  background: transparent !important;
  border: none !important;
  outline: none !important;
  box-shadow: none !important;
  padding: 0 !important;
  margin: 0 !important;
  font-weight: 400 !important;
  width: 100% !important;
}
/* ç¦ç”¨ä¸Žåªè¯»çŠ¶æ€ */
:deep(.u-input input:disabled),
:deep(.u-input__input:disabled) {
  color: #999 !important;
  -webkit-text-fill-color: #999 !important;
  background: transparent !important;
}
:deep(.u-input input[readonly]),
:deep(.u-input__input[readonly]) {
  color: #666 !important;
  font-weight: 400 !important;
  background: transparent !important;
}
/* å ä½ç¬¦æ ·å¼ */
:deep(.u-input input::placeholder),
:deep(.u-input__input::placeholder) {
  color: #c0c4cc !important;
  font-size: 15px !important;
  font-weight: 400 !important;
}
.account-detail {
  min-height: 100vh;
  background: #f5f5f5;
  padding-bottom: 5rem;
}
/* äº§å“ä¿¡æ¯åŒºåŸŸ */
.product-section {
  background: #fff;
  margin-top: 10px;
  padding: 16px;
}
.section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 16px;
  width: 100%;
}
.section-title {
  font-size: 16px;
  font-weight: 500;
  color: #333;
}
/* äº§å“å¡ç‰‡æ ·å¼ */
.product-card {
  background: #ffffff;
  border-radius: 8px;
  padding: 16px;
  margin-bottom: 16px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.tip-text {
  margin: 2px 20px;
}
.product-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-bottom: 12px;
  margin-bottom: 16px;
}
.product-title {
  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;
}
.product-productCategory {
  font-size: 15px;
  font-weight: 500;
  color: #333;
}
/* äº§å“è¡¨å•项样式 */
.product-form {
  margin: 0;
}
.product-form :deep(.u-form-item) {
  background: transparent;
  margin-bottom: 0;
  padding: 0;
}
.product-form :deep(.u-form-item__body) {
  padding: 12px 0;
  min-height: 48px;
}
.product-form :deep(.u-form-item__label) {
  font-size: 14px !important;
  color: #666 !important;
  width: 100px;
  font-weight: 400;
}
.product-form :deep(.u-input__content__field-wrapper__field),
.product-form :deep(.u-input__input) {
  font-size: 14px !important;
  color: #333 !important;
  text-align: right;
}
/* äº§å“è¡¨å•最后一项去掉底边框 */
.product-form :deep(.u-form-item:last-child .u-form-item__body) {
  border-bottom: none;
}
/* é€‰æ‹©å™¨å¼¹çª—样式 */
.picker-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 16px 20px;
  background: #fff;
}
.picker-cancel {
  color: #666;
  font-size: 16px;
}
.picker-title {
  color: #333;
  font-size: 16px;
  font-weight: 500;
}
.picker-confirm {
  color: #007aff;
  font-size: 16px;
}
/* æ ‘选择器弹窗样式 */
.popup-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 16px 20px;
  background: #fff;
  position: sticky;
  top: 0;
  z-index: 10;
}
.cancelButton {
  color: #666;
  font-size: 16px;
}
.confirmButton {
  color: #007aff;
  font-size: 16px;
}
.u-tree {
  height: 300px;
  padding: 16px;
}
/* é¡µé¢åŸºç¡€æ ·å¼ */
.page-container {
  min-height: 100vh;
  background: #f5f5f5;
  padding-bottom: 5rem;
}
/* å“åº”式调整 */
@media (max-width: 375px) {
  :deep(.u-form-item__label) {
    width: 100px !important;
    font-size: 14px !important;
  }
  :deep(.u-input__content__field-wrapper__field),
  :deep(.u-input__input) {
    font-size: 14px !important;
  }
}
src/store/modules/user.ts
@@ -13,55 +13,55 @@
const useUserStore = defineStore("user", {
  state: () => ({
    token: getToken(),
      id: "",
    id: "",
    name: "",
    avatar: "",
      currentFactoryName: "",
      nickName: "",
      roleName: "",
      currentDeptId: "",
      currentLoginTime: "",
    currentFactoryName: "",
    nickName: "",
    roleName: "",
    currentDeptId: "",
    currentLoginTime: "",
    roles: Array(),
    permissions: [],
  }),
  actions: {
      // éƒ¨é—¨ç™»å½•
      loginCheckFactory(userInfo: any) {
          const username = userInfo.username.trim()
          const password = userInfo.password
          const factoryId = userInfo.factoryId
          return new Promise((resolve, reject) => {
              loginCheckFactory(username, password, factoryId).then((res: any) => {
                  setToken(res.token)
                  this.token = res.token
                  resolve(null)
              }).catch((error: any) => {
                  reject(error)
              })
          })
      },
    // éƒ¨é—¨ç™»å½•
    loginCheckFactory(userInfo: any) {
      const username = userInfo.username.trim()
      const password = userInfo.password
      const factoryId = userInfo.factoryId
      return new Promise((resolve, reject) => {
        loginCheckFactory(username, password, factoryId).then((res: any) => {
          setToken(res.token)
          this.token = res.token
          resolve(null)
        }).catch((error: any) => {
          reject(error)
        })
      })
    },
    // èŽ·å–ç”¨æˆ·ä¿¡æ¯
    getInfo() {
      return new Promise((resolve, reject) => {
        getInfo()
          .then((res: any) => {
              const user = res.user
              let avatar = user.avatar || ""
              avatar = config.baseUrl + '/profile/' + avatar
              if (res.roles && res.roles.length > 0) { // éªŒè¯è¿”回的roles是否是一个非空数组
                  this.roles = res.roles
                  this.permissions = res.permissions
              } else {
                  this.roles = ['ROLE_DEFAULT']
              }
              this.id = user.userId
              this.name = user.userName
              this.avatar = avatar
              this.currentFactoryName = user.currentFactoryName
              this.nickName = user.nickName
              this.roleName = user.roles[0].roleName
              this.currentDeptId = user.tenantId
              this.currentLoginTime = this.getCurrentTime()
            const user = res.user
            let avatar = user.avatar || ""
            avatar = config.baseUrl + '/profile/' + avatar
            if (res.roles && res.roles.length > 0) { // éªŒè¯è¿”回的roles是否是一个非空数组
              this.roles = res.roles
              this.permissions = res.permissions
            } else {
              this.roles = ['ROLE_DEFAULT']
            }
            this.id = user.userId
            this.name = user.userName
            this.avatar = avatar
            this.currentFactoryName = user.currentFactoryName
            this.nickName = user.nickName
            this.roleName = user.roles[0].roleName
            this.currentDeptId = user.tenantId
            this.currentLoginTime = this.getCurrentTime()
            resolve(res);
          })
          .catch((error) => {
@@ -87,16 +87,16 @@
          });
      });
    },
      getCurrentTime() {
          const now = new Date();
          const year = now.getFullYear();       // èŽ·å–å¹´ä»½
          const month = String(now.getMonth() + 1).padStart(2, '0');  // æœˆä»½ä»Ž0开始,要+1,并补零
          const day = String(now.getDate()).padStart(2, '0');         // æ—¥æœŸè¡¥é›¶
          const hours = String(now.getHours()).padStart(2, '0');      // å°æ—¶è¡¥é›¶
          const minutes = String(now.getMinutes()).padStart(2, '0');  // åˆ†é’Ÿè¡¥é›¶
          const seconds = String(now.getSeconds()).padStart(2, '0');  // ç§’数补零
          return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
      },
    getCurrentTime() {
      const now = new Date();
      const year = now.getFullYear();       // èŽ·å–å¹´ä»½
      const month = String(now.getMonth() + 1).padStart(2, '0');  // æœˆä»½ä»Ž0开始,要+1,并补零
      const day = String(now.getDate()).padStart(2, '0');         // æ—¥æœŸè¡¥é›¶
      const hours = String(now.getHours()).padStart(2, '0');      // å°æ—¶è¡¥é›¶
      const minutes = String(now.getMinutes()).padStart(2, '0');  // åˆ†é’Ÿè¡¥é›¶
      const seconds = String(now.getSeconds()).padStart(2, '0');  // ç§’数补零
      return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
    },
  },
});
src/uni_modules/uni-scss/styles/setting/_variables.scss
@@ -1,4 +1,4 @@
// @import "sass:math";
// @use "sass:math";
@import  '../tools/functions.scss';
// é—´è·åŸºç¡€å€æ•°
$uni-space-root: 2 !default;
src/utils/ruoyi.js
@@ -224,3 +224,33 @@
      return true;
    }
}
/**
* å°†æ—¥æœŸè½¬æ¢ä¸ºYYYY-MM-DD格式
* @param {*} dateSource æ—¥æœŸæºï¼ˆå¯ä»¥æ˜¯æ—¶é—´æˆ³æˆ–Date对象)
* @returns æ ¼å¼åŒ–后的日期字符串 YYYY-MM-DD
*/
export function formatDateToYMD(dateSource) {
    let date;
    // å¤„理:如果是时间戳,先转为Date对象
    if (typeof dateSource === 'number') {
        date = new Date(dateSource);
    }
    // å¤„理:如果是Date对象,直接使用
    else if (dateSource instanceof Date) {
        date = dateSource;
    }
    // å¼‚常情况:返回空
    else {
        return '';
    }
    // è¡¥é›¶å‡½æ•°ï¼šç¡®ä¿æœˆ/日是两位数
    const padZero = (num) => num.toString().padStart(2, '0');
    const year = date.getFullYear();
    const month = padZero(date.getMonth() + 1);
    const day = padZero(date.getDate());
    return `${year}-${month}-${day}`;
}
yarn.lock
ÎļþÌ«´ó