From 00cd513292bad1db6b078acea6a1cd89578b1586 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期三, 06 五月 2026 13:31:15 +0800
Subject: [PATCH] 升级pro 1.财务页面删除掉一些功能 2.财务管理应付管理、应收管理逻辑修改 3.生成凭证页面重构 4.整体样式修改

---
 src/views/financialManagement/voucher/index.vue |  576 +++++++++++++++++++++++++++++++++++++++++++++++++-------
 1 files changed, 497 insertions(+), 79 deletions(-)

diff --git a/src/views/financialManagement/voucher/index.vue b/src/views/financialManagement/voucher/index.vue
index 440336f..817185c 100644
--- a/src/views/financialManagement/voucher/index.vue
+++ b/src/views/financialManagement/voucher/index.vue
@@ -69,87 +69,142 @@
       </PIMTable>
     </div>
 
-    <el-dialog :title="dialogTitle" v-model="dialogVisible" width="900px" append-to-body>
-      <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
-        <el-row :gutter="20">
-          <el-col :span="8">
-            <el-form-item label="鍑瘉瀛楀彿" prop="voucherNo">
-              <el-input v-model="form.voucherNo" placeholder="绯荤粺鑷姩鐢熸垚" disabled />
-            </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item label="鍑瘉鏃ユ湡" prop="voucherDate">
-              <el-date-picker v-model="form.voucherDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item label="闄勪欢寮犳暟" prop="attachmentCount">
-              <el-input-number v-model="form.attachmentCount" :min="0" style="width: 100%;" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-form-item label="鍑瘉鍒嗗綍" prop="entries">
-          <el-table :data="form.entries" border style="width: 100%">
-            <el-table-column type="index" label="搴忓彿" width="60" />
-            <el-table-column prop="subjectCode" label="绉戠洰缂栫爜" width="120">
-              <template #default="{ $index }">
-                <el-select v-model="form.entries[$index].subjectCode" placeholder="閫夋嫨绉戠洰" filterable style="width: 100%;" @change="(val) => handleSubjectChange(val, $index)">
-                  <el-option v-for="item in subjectList" :key="item.code" :label="item.code" :value="item.code" />
-                </el-select>
-              </template>
-            </el-table-column>
-            <el-table-column prop="subjectName" label="绉戠洰鍚嶇О" width="150">
-              <template #default="{ $index }">
-                <el-input v-model="form.entries[$index].subjectName" disabled />
-              </template>
-            </el-table-column>
-            <el-table-column prop="summary" label="鎽樿">
-              <template #default="{ $index }">
-                <el-input v-model="form.entries[$index].summary" placeholder="璇疯緭鍏ユ憳瑕�" />
-              </template>
-            </el-table-column>
-            <el-table-column prop="debit" label="鍊熸柟閲戦" width="130">
-              <template #default="{ $index }">
-                <el-input-number v-model="form.entries[$index].debit" :min="0" :precision="2" style="width: 100%;" @change="calculateTotal" />
-              </template>
-            </el-table-column>
-            <el-table-column prop="credit" label="璐锋柟閲戦" width="130">
-              <template #default="{ $index }">
-                <el-input-number v-model="form.entries[$index].credit" :min="0" :precision="2" style="width: 100%;" @change="calculateTotal" />
-              </template>
-            </el-table-column>
-            <el-table-column label="鎿嶄綔" width="80">
-              <template #default="{ $index }">
-                <el-button type="danger" link @click="removeEntry($index)">鍒犻櫎</el-button>
-              </template>
-            </el-table-column>
-          </el-table>
-          <div style="display: flex; justify-content: space-between; margin-top: 10px;">
-            <el-button type="primary" link @click="addEntry">+ 娣诲姞鍒嗗綍</el-button>
-            <div>
-              <span style="margin-right: 20px;">鍚堣: 鍊熸柟 <span :class="totalDebitEntry === totalCreditEntry ? 'text-success' : 'text-danger'">楼{{ formatMoney(totalDebitEntry) }}</span></span>
-              <span>璐锋柟 <span :class="totalDebitEntry === totalCreditEntry ? 'text-success' : 'text-danger'">楼{{ formatMoney(totalCreditEntry) }}</span></span>
+    <FormDialog :title="dialogTitle" v-model="dialogVisible" width="1200px" @confirm="submitForm" @cancel="dialogVisible = false">
+      <div class="voucher-container">
+        <div class="voucher-header">
+          <h2 class="voucher-title">璁拌处鍑瘉</h2>
+          <div class="voucher-period">{{ form.voucherDate ? form.voucherDate.substring(0, 7) + '鏈�' : '' }}</div>
+        </div>
+        <el-form :model="form" :rules="rules" ref="formRef" label-width="0">
+          <div class="voucher-info">
+            <div class="voucher-no-section">
+              <span class="label">鍑瘉瀛楋細</span>
+              <el-select v-model="form.voucherPrefix" style="width: 70px;">
+                <el-option label="璁�" value="璁�" />
+              </el-select>
+              <el-input v-model="form.voucherNum" style="width: 60px;" />
+              <span class="label" style="margin-left: 5px;">鍙�</span>
+            </div>
+            <div class="voucher-date-section">
+              <span class="label">鏃ユ湡锛�</span>
+              <el-date-picker v-model="form.voucherDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 140px;" />
+            </div>
+            <div class="voucher-attachment-section">
+              <span class="label">闄勪欢锛�</span>
+              <el-input-number v-model="form.attachmentCount" :min="0" :controls="false" style="width: 60px;" />
+              <span class="label" style="margin-left: 5px;">寮�</span>
+              <el-button type="primary" link style="margin-left: 10px;">涓婁紶鏂囦欢</el-button>
             </div>
           </div>
-        </el-form-item>
-        <el-form-item label="鍒跺崟浜�" prop="creator">
-          <el-input v-model="form.creator" disabled />
-        </el-form-item>
-        <el-form-item label="澶囨敞" prop="remark">
-          <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="璇疯緭鍏ュ娉�" />
-        </el-form-item>
-      </el-form>
+          <div class="voucher-table">
+            <table class="accounting-voucher">
+              <thead>
+                <tr>
+                  <th class="col-summary" rowspan="2">鎽樿</th>
+                  <th class="col-subject" rowspan="2">浼氳绉戠洰</th>
+                  <th class="col-debit-header" colspan="11">鍊熸柟</th>
+                  <th class="col-credit-header" colspan="11">璐锋柟</th>
+                  <th class="col-action" rowspan="2">鎿嶄綔</th>
+                </tr>
+                <tr class="amount-header">
+                  <th>浜�</th>
+                  <th>鍗�</th>
+                  <th>鐧�</th>
+                  <th>鍗�</th>
+                  <th>涓�</th>
+                  <th>鍗�</th>
+                  <th>鐧�</th>
+                  <th>鍗�</th>
+                  <th>鍏�</th>
+                  <th>瑙�</th>
+                  <th>鍒�</th>
+                  <th>浜�</th>
+                  <th>鍗�</th>
+                  <th>鐧�</th>
+                  <th>鍗�</th>
+                  <th>涓�</th>
+                  <th>鍗�</th>
+                  <th>鐧�</th>
+                  <th>鍗�</th>
+                  <th>鍏�</th>
+                  <th>瑙�</th>
+                  <th>鍒�</th>
+                </tr>
+              </thead>
+              <tbody>
+                <tr v-for="(entry, rowIndex) in form.entries" :key="rowIndex" @click="selectRow(rowIndex)" :class="{ 'selected-row': selectedRowIndex === rowIndex }">
+                  <td class="col-summary">
+                    <el-input v-model="entry.summary" placeholder="璇疯緭鍏ユ憳瑕�" @focus="selectRow(rowIndex)" />
+                  </td>
+                  <td class="col-subject">
+                    <el-select v-model="entry.subjectCode" placeholder="閫夋嫨绉戠洰" filterable @change="(val) => handleSubjectChange(val, rowIndex)" @focus="selectRow(rowIndex)">
+                      <el-option v-for="item in subjectList" :key="item.code" :label="item.code + item.name" :value="item.code" />
+                    </el-select>
+                    <div class="subject-name">{{ entry.subjectName }}</div>
+                  </td>
+                  <!-- 鍊熸柟11鍒� -->
+                  <template v-if="editingCell.row === rowIndex && editingCell.type === 'debit'">
+                    <td colspan="11" class="debit-input-cell">
+                      <el-input-number ref="amountInputRef" v-model="entry.debit" :min="0" :precision="2" :controls="false" size="small" @blur="finishEdit" class="full-width-input" />
+                    </td>
+                  </template>
+                  <template v-else>
+                    <td v-for="(digit, dIndex) in getAmountDigits(entry.debit, 11)" :key="'debit-'+dIndex" class="amount-cell debit-cell" @click="openAmountInput(rowIndex, 'debit')">
+                      <span :class="{ 'text-primary': digit !== '', 'zero': digit === '' }">{{ digit || '' }}</span>
+                    </td>
+                  </template>
+                  <!-- 璐锋柟11鍒� -->
+                  <template v-if="editingCell.row === rowIndex && editingCell.type === 'credit'">
+                    <td colspan="11" class="credit-input-cell">
+                      <el-input-number ref="amountInputRef" v-model="entry.credit" :min="0" :precision="2" :controls="false" size="small" @blur="finishEdit" class="full-width-input" />
+                    </td>
+                  </template>
+                  <template v-else>
+                    <td v-for="(digit, dIndex) in getAmountDigits(entry.credit, 11)" :key="'credit-'+dIndex" class="amount-cell credit-cell" @click="openAmountInput(rowIndex, 'credit')">
+                      <span :class="{ 'text-danger': digit !== '', 'zero': digit === '' }">{{ digit || '' }}</span>
+                    </td>
+                  </template>
+                  <td class="col-action">
+                    <el-button type="danger" link size="small" @click="removeEntry(rowIndex)" icon="Delete" :disabled="form.entries.length <= 2">鍒犻櫎</el-button>
+                  </td>
+                </tr>
+                <tr class="total-row">
+                  <td class="col-summary" colspan="2" style="text-align: center; font-weight: bold;">鍚堣锛�</td>
+                  <td v-for="(digit, index) in getAmountDigits(totalDebitEntry, 11)" :key="'total-debit-'+index" class="amount-cell total-debit-cell">
+                    <span :class="{ 'text-primary': digit !== '' }">{{ digit }}</span>
+                  </td>
+                  <td v-for="(digit, index) in getAmountDigits(totalCreditEntry, 11)" :key="'total-credit-'+index" class="amount-cell total-credit-cell">
+                    <span :class="{ 'text-danger': digit !== '' }">{{ digit }}</span>
+                  </td>
+                  <td class="col-action"></td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+          <div class="voucher-toolbar">
+            <el-button type="primary" link @click="addEntry" icon="Plus">鏂板琛�</el-button>
+          </div>
+          <div class="voucher-footer">
+            <div class="creator-section">
+              <span class="label">鍒跺崟浜猴細{{ form.creator }}</span>
+            </div>
+          </div>
+        </el-form>
+      </div>
       <template #footer>
-        <el-button @click="dialogVisible = false">鍙栨秷</el-button>
-        <el-button type="primary" @click="submitForm" :disabled="totalDebitEntry !== totalCreditEntry">纭畾</el-button>
+        <div>
+          <el-button type="primary" @click="submitForm" :disabled="!isBalanced">淇濆瓨</el-button>
+          <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+        </div>
       </template>
-    </el-dialog>
+    </FormDialog>
   </div>
 </template>
 
 <script setup>
-import { ref, reactive, onMounted, computed } from "vue";
+import { ref, reactive, onMounted, computed, nextTick } from "vue";
 import { ElMessage, ElMessageBox } from "element-plus";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
 
 defineOptions({
   name: "鍑瘉绠$悊",
@@ -198,11 +253,24 @@
 
 const form = reactive({
   voucherNo: "",
+  voucherPrefix: "璁�",
+  voucherNum: "",
   voucherDate: "",
   attachmentCount: 0,
   entries: [],
   creator: "寮犱笁",
   remark: "",
+});
+
+const selectedRowIndex = ref(-1);
+const editingCell = reactive({
+  row: -1,
+  type: "",
+});
+const amountInputRef = ref(null);
+
+const isBalanced = computed(() => {
+  return totalDebitEntry.value === totalCreditEntry.value && totalDebitEntry.value > 0;
 });
 
 const rules = {
@@ -283,6 +351,56 @@
   form.entries.push({ subjectCode: "", subjectName: "", summary: "", debit: 0, credit: 0 });
 };
 
+const selectRow = (index) => {
+  selectedRowIndex.value = index;
+};
+
+const openAmountInput = (index, type) => {
+  editingCell.row = index;
+  editingCell.type = type;
+  nextTick(() => {
+    if (amountInputRef.value) {
+      amountInputRef.value.focus();
+    }
+  });
+};
+
+const finishEdit = () => {
+  editingCell.row = -1;
+  editingCell.type = "";
+};
+
+const getAmountDigits = (amount, length) => {
+  if (!amount || amount === 0) {
+    return new Array(length).fill('');
+  }
+
+  const amountStr = Number(amount).toFixed(2);
+  const [intPart, decPart] = amountStr.split('.');
+  const fullAmount = intPart + decPart;
+
+  // 宸﹀~鍏�0鍒版寚瀹氶暱搴�
+  const paddedAmount = fullAmount.padStart(length, '0');
+  const digits = paddedAmount.split('');
+
+  // 鎵惧埌绗竴涓潪闆舵暟瀛楃殑浣嶇疆
+  let firstNonZeroIndex = 0;
+  for (let i = 0; i < digits.length; i++) {
+    if (digits[i] !== '0') {
+      firstNonZeroIndex = i;
+      break;
+    }
+  }
+
+  // 鍙殣钘忓墠瀵奸浂锛堢涓�涓潪闆舵暟瀛椾箣鍓嶇殑闆讹級
+  return digits.map((d, index) => {
+    if (index < firstNonZeroIndex) {
+      return ''; // 鍓嶅闆舵樉绀轰负绌�
+    }
+    return d; // 淇濈暀閲戦涓殑闆�
+  });
+};
+
 const removeEntry = (index) => {
   form.entries.splice(index, 1);
   calculateTotal();
@@ -302,14 +420,23 @@
 const add = () => {
   isEdit.value = false;
   dialogTitle.value = "鏂板鍑瘉";
+  const nextNum = String(mockData.length + 1).padStart(2, "0");
   Object.assign(form, {
-    voucherNo: "璁�-" + String(mockData.length + 1).padStart(4, "0"),
+    voucherNo: "璁�-" + nextNum,
+    voucherPrefix: "璁�",
+    voucherNum: nextNum,
     voucherDate: new Date().toISOString().split('T')[0],
     attachmentCount: 0,
-    entries: [{ subjectCode: "", subjectName: "", summary: "", debit: 0, credit: 0 }],
+    entries: [
+      { subjectCode: "", subjectName: "", summary: "", debit: 0, credit: 0 },
+      { subjectCode: "", subjectName: "", summary: "", debit: 0, credit: 0 },
+      { subjectCode: "", subjectName: "", summary: "", debit: 0, credit: 0 },
+      { subjectCode: "", subjectName: "", summary: "", debit: 0, credit: 0 },
+    ],
     creator: "寮犱笁",
     remark: "",
   });
+  selectedRowIndex.value = 0;
   dialogVisible.value = true;
 };
 
@@ -317,7 +444,18 @@
   isEdit.value = true;
   currentId.value = row.id;
   dialogTitle.value = "缂栬緫鍑瘉";
-  Object.assign(form, row);
+  const parts = row.voucherNo.split('-');
+  Object.assign(form, {
+    ...row,
+    voucherPrefix: parts[0] || '璁�',
+    voucherNum: parts[1] || '',
+  });
+  if (form.entries.length < 4) {
+    while (form.entries.length < 4) {
+      form.entries.push({ subjectCode: "", subjectName: "", summary: "", debit: 0, credit: 0 });
+    }
+  }
+  selectedRowIndex.value = 0;
   dialogVisible.value = true;
 };
 
@@ -366,20 +504,33 @@
 const submitForm = () => {
   formRef.value.validate((valid) => {
     if (valid) {
-      if (totalDebitEntry.value !== totalCreditEntry.value) {
+      if (!isBalanced.value) {
         ElMessage.error("鍊熻捶涓嶅钩琛★紝璇锋鏌ュ垎褰�");
         return;
       }
-      const summary = form.entries.find(e => e.debit > 0)?.summary || "";
+
+      const validEntries = form.entries.filter(e => e.subjectCode && (e.debit > 0 || e.credit > 0));
+      const summary = validEntries.find(e => e.debit > 0)?.summary || "";
+
+      const voucherNo = `${form.voucherPrefix}-${form.voucherNum}`;
+      const dataToSave = {
+        ...form,
+        voucherNo,
+        summary,
+        debit: totalDebitEntry.value,
+        credit: totalCreditEntry.value,
+        entries: validEntries,
+      };
+
       if (isEdit.value) {
         const index = mockData.findIndex(item => item.id === currentId.value);
         if (index !== -1) {
-          mockData[index] = { ...mockData[index], ...form, summary, debit: totalDebitEntry.value, credit: totalCreditEntry.value };
+          mockData[index] = { ...mockData[index], ...dataToSave };
         }
         ElMessage.success("缂栬緫鎴愬姛");
       } else {
         const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
-        mockData.push({ id: newId, ...form, summary, debit: totalDebitEntry.value, credit: totalCreditEntry.value, status: "unposted" });
+        mockData.push({ id: newId, ...dataToSave, status: "unposted" });
         ElMessage.success("鏂板鎴愬姛");
       }
       dialogVisible.value = false;
@@ -415,4 +566,271 @@
   color: #f56c6c;
   font-weight: bold;
 }
+
+.text-primary {
+  color: #409eff;
+}
+
+.voucher-container {
+  background: #fff;
+  padding: 20px;
+}
+
+.voucher-header {
+  text-align: center;
+  margin-bottom: 15px;
+
+  .voucher-title {
+    font-size: 22px;
+    font-weight: bold;
+    margin: 0 0 5px 0;
+    color: #303133;
+  }
+
+  .voucher-period {
+    font-size: 14px;
+    color: #909399;
+  }
+}
+
+.voucher-info {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 15px;
+  padding: 0 10px;
+
+  .label {
+    font-size: 14px;
+    color: #606266;
+  }
+
+  .voucher-no-section,
+  .voucher-date-section,
+  .voucher-attachment-section {
+    display: flex;
+    align-items: center;
+  }
+}
+
+.voucher-table {
+  border: 1px solid #dcdfe6;
+  border-right: none;
+  margin-bottom: 15px;
+}
+
+.accounting-voucher {
+  width: 100%;
+  border-collapse: collapse;
+  font-size: 13px;
+
+  th,
+  td {
+    border: 1px solid #dcdfe6;
+    text-align: center;
+    padding: 0;
+    height: 36px;
+  }
+
+  & th:last-child,
+  & td:last-child {
+    border-right: none !important;
+  }
+
+  thead {
+    background-color: #f5f7fa;
+
+    th {
+      font-weight: normal;
+      color: #606266;
+      font-size: 12px;
+    }
+
+    .col-summary,
+    .col-subject {
+      font-weight: bold;
+      font-size: 13px;
+    }
+
+    .col-debit-header,
+    .col-credit-header {
+      background-color: #ecf5ff;
+      color: #409eff;
+      font-weight: bold;
+    }
+  }
+
+  .amount-header {
+    th {
+      font-size: 11px;
+      padding: 2px 0;
+      background-color: #f5f7fa;
+    }
+  }
+
+  .col-summary {
+    width: 160px;
+    min-width: 160px;
+  }
+
+  .col-subject {
+    width: 180px;
+    min-width: 180px;
+  }
+
+  .col-action {
+    width: 60px;
+    min-width: 60px;
+    text-align: center;
+  }
+
+  .amount-cell {
+    width: 24px;
+    min-width: 24px;
+    max-width: 24px;
+    padding: 0;
+    font-size: 13px;
+    font-family: 'Courier New', monospace;
+    cursor: pointer;
+    text-align: center;
+
+    &:hover {
+      background-color: #f5f7fa;
+    }
+
+    span {
+      display: block;
+      width: 100%;
+      height: 100%;
+      line-height: 36px;
+
+      &.zero {
+        color: #c0c4cc;
+      }
+    }
+  }
+
+  .debit-input-cell,
+  .credit-input-cell {
+    padding: 0;
+    background-color: #ecf5ff;
+
+    .full-width-input {
+      width: 100%;
+
+      :deep(.el-input__wrapper) {
+        padding: 0 10px;
+        box-shadow: none;
+        background-color: transparent;
+      }
+
+      input {
+        text-align: right;
+        font-size: 14px;
+        height: 34px;
+      }
+    }
+  }
+
+  tbody {
+    tr {
+      &:hover {
+        background-color: #f5f7fa;
+      }
+
+      &.selected-row {
+        background-color: #ecf5ff;
+      }
+    }
+
+    td {
+      .el-input {
+        .el-input__wrapper {
+          box-shadow: none;
+          padding: 0 5px;
+        }
+
+        input {
+          text-align: center;
+          height: 34px;
+        }
+      }
+
+      .el-select {
+        width: 100%;
+
+        .el-input__wrapper {
+          box-shadow: none;
+        }
+
+        input {
+          text-align: center;
+          height: 34px;
+        }
+      }
+    }
+
+    .col-summary {
+      .el-input input {
+        text-align: left;
+        padding-left: 10px;
+      }
+    }
+
+    .col-subject {
+      position: relative;
+
+      .el-select {
+        .el-input input {
+          font-size: 12px;
+        }
+      }
+
+      .subject-name {
+        font-size: 11px;
+        color: #909399;
+        margin-top: 2px;
+        line-height: 1.2;
+      }
+    }
+  }
+
+  .total-row {
+    background-color: #fdf6ec;
+
+    td {
+      font-weight: bold;
+    }
+
+    .total-cell {
+      background-color: #fdf6ec;
+      font-weight: bold;
+    }
+  }
+}
+
+.voucher-toolbar {
+  display: flex;
+  justify-content: flex-start;
+  padding: 10px 0;
+  margin-top: 5px;
+}
+
+.voucher-footer {
+  display: flex;
+  justify-content: flex-end;
+  padding: 0 10px;
+  margin-top: 10px;
+
+  .creator-section {
+    .label {
+      font-size: 14px;
+      color: #606266;
+    }
+  }
+}
+
+:deep(.el-dialog__body) {
+  padding: 10px 20px;
+}
 </style>

--
Gitblit v1.9.3