sen
2026-02-01 93f8c736fd50a80a72d633e888e2d65904bcd7fc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
<template>
  <el-dialog 
    v-model="visible" 
    :title="dialogTitle" 
    width="1200px" 
    destroy-on-close
  >
    <el-descriptions title="账单基本信息" :column="3" border class="mb-5">
      <el-descriptions-item label="系统编号">{{ billInfo.systemNo }}</el-descriptions-item>
      <el-descriptions-item label="账单名称">{{ billInfo.billName }}</el-descriptions-item>
      <el-descriptions-item :label="type.includes('应收') ? '客户名称' : '供应商名称'">
        {{ type.includes('应收') ? billInfo.customerName : billInfo.supplierName }}
      </el-descriptions-item>
      <el-descriptions-item label="单据数量">{{ billInfo.documentCount }}</el-descriptions-item>
      <el-descriptions-item label="应结算金额"><span class="text-bold">{{ billInfo.totalAmount }}</span></el-descriptions-item>
      <el-descriptions-item label="币制">{{ billInfo.currency }}</el-descriptions-item>
      <el-descriptions-item label="减免金额">{{ billInfo.discountAmount }}</el-descriptions-item>
      <el-descriptions-item :label="type.includes('应收') ? '已收金额' : '已付金额'">{{ billInfo.receivedAmount }}</el-descriptions-item>
      <el-descriptions-item :label="type.includes('应收') ? '待收金额' : '待付金额'">
        <span class="text-danger">{{ billInfo.pendingAmount }}</span>
      </el-descriptions-item>
      <el-descriptions-item label="周期类型">{{ billInfo.periodType }}</el-descriptions-item>
      <el-descriptions-item label="业务期间">{{ billInfo.businessStartDate }} ~ {{ billInfo.businessEndDate }}</el-descriptions-item>
      <el-descriptions-item label="账单周期">{{ billInfo.billingStartDate }} ~ {{ billInfo.billingEndDate }}</el-descriptions-item>
    </el-descriptions>
 
    <div  v-if="InvoiceDetails !== '开票明细'"  class="section-header">
      <h3 class="section-title">发票商品明细</h3>
      <el-button 
       
        type="primary" 
        size="small" 
        icon="Plus" 
        @click="addItemRow"
      >新增</el-button>
    </div>
    
    <el-table  v-if="InvoiceDetails !== '开票明细'"  :data="form.invoiceBillDetails" border class="mb-5">
      <el-table-column label="商品名称" prop="goodsName">
        <template #default="{ row }">
          <el-input v-if="!row.isSaved && InvoiceDetails !== '开票明细'" v-model="row.goodsName" placeholder="商品名称" />
          <span v-else>{{ row.goodsName }}</span>
        </template>
      </el-table-column>
      <el-table-column label="单价" prop="price" width="200">
        <template #default="{ row }">
          <el-input-number v-if="!row.isSaved && InvoiceDetails !== '开票明细'" v-model="row.price" :precision="2" style="width:100%" />
          <span v-else>{{ row.price }}</span>
        </template>
      </el-table-column>
      <el-table-column label="含税标志" prop="withTaxFlag" width="200">
        <template #default="{ row }">
          <el-radio-group v-model="row.withTaxFlag" :disabled="row.isSaved || InvoiceDetails === '开票明细'">
            <el-radio :label="0">含税</el-radio>
            <el-radio :label="1">不含税</el-radio>
          </el-radio-group>
        </template>
      </el-table-column>
      <el-table-column v-if="InvoiceDetails !== '开票明细'" label="操作" width="150" align="center">
        <template #default="{ row, $index }">
          <el-button v-if="!row.isSaved" type="primary" link @click="saveItemRow(row)">保存</el-button>
          <el-button v-else type="primary" link @click="row.isSaved = false">修改</el-button>
          <el-button type="danger" link @click="form.invoiceBillDetails.splice($index, 1)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
 
    <template v-if="InvoiceDetails !== '开票明细'">
      <h3 class="section-title">本次开票信息</h3>
      <el-form ref="formRef" :model="form" :rules="formRules" label-width="130px">
        <el-row :gutter="20">
          <el-col :span="8">
            <el-form-item label="抬头公司" prop="invoiceCompanyName">
              <el-input v-model="form.invoiceCompanyName" placeholder="请输入" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="社会信用代码" prop="invoiceCreditCode">
              <el-input v-model="form.invoiceCreditCode" placeholder="请输入" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="发票类型" prop="invoiceType">
              <el-select v-model="form.invoiceType" placeholder="请选择" style="width:100%">
                <el-option v-for="d in sys_invoice_type" :key="d.value" :label="d.label" :value="d.value" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="8"><el-form-item label="开户银行"><el-input v-model="form.invoiceBankName" /></el-form-item></el-col>
          <el-col :span="8"><el-form-item label="基本账号"><el-input v-model="form.invoiceBankNo" /></el-form-item></el-col>
          <el-col :span="8"><el-form-item label="已开票金额"><el-input v-model="billInfo.invoicedAmount" disabled /></el-form-item></el-col>
          <el-col :span="8">
            <el-form-item label="本次开票金额" prop="currentInvoicedAmount">
              <el-input-number v-model="form.currentInvoicedAmount" disabled :precision="2" style="width:100%" />
            </el-form-item>
          </el-col>
          <el-col :span="16"><el-form-item label="发票备注"><el-input v-model="form.invoiceRemark" type="textarea" /></el-form-item></el-col>
        </el-row>
      </el-form>
    </template>
 
    <template v-else>
      <h3 class="section-title">开票明细记录</h3>
      <el-table v-loading="loading" :data="recordList" border stripe>
        <el-table-column label="发票类型" prop="invoiceType" align="center" />
        <el-table-column label="发票编号" prop="invoiceNo" align="center" />
        <el-table-column label="开票状态" prop="status" align="center" />
        <el-table-column label="开票日期" prop="invoiceDate" align="center" />
        <el-table-column label="发票金额" prop="invoiceAmount" align="center" />
        <el-table-column label="购买方名称" prop="buyerName" align="center" />
        <el-table-column label="销售方名称" prop="sellerName" align="center" />
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="fetchRecords" />
    </template>
 
    <template #footer>
      <el-button v-if="InvoiceDetails !== '开票明细'" type="primary" @click="handleConfirm">确定开票</el-button>
      <el-button @click="openIshpw">关闭</el-button>
    </template>
  </el-dialog>
</template>
 
<script setup lang="ts">
import { ref, reactive, computed,watch } from 'vue'
import useCurrentInstance from "@/utils/useCurrentInstance";
 
const { proxy } = useCurrentInstance();
const { sys_invoice_type } = proxy.useDict('sys_invoice_type');
 
const props = defineProps<{
  type: string,           // '应收账单开票' 等
  InvoiceDetails: string  // '本次开票信息' 或 '开票明细'
}>()
 
const visible = ref(false)
const loading = ref(false)
const billInfo = ref<any>({})
const recordList = ref([])
const total = ref(0)
const formRef = ref()
 
const dialogTitle = computed(() => {
  return props.InvoiceDetails === '开票明细' ? `${props.type}记录` : props.type;
})
 
const form = reactive({
  invoiceCompanyName: '',
  invoiceCreditCode: '',
  invoiceType: '',
  currentInvoicedAmount: 0,
  invoiceBillDetails: [] as any[]
})
 
const formRules = {
  invoiceCompanyName: [{ required: true, message: '请输入抬头', trigger: 'blur' }],
  invoiceType: [{ required: true, message: '请选择类型', trigger: 'change' }],
  // currentInvoicedAmount: [{ required: true, message: '请输入金额', trigger: 'blur' }]
}
 
const queryParams = reactive({ pageNum: 1, pageSize: 10, billId: '' })
 
// 模拟获取开票记录
const fetchRecords = async () => {
  loading.value = true;
  // 这里根据 props.type 调用对应的 API (应收/应付记录)
  // const res = await listInvoiceRecords(queryParams);
  // recordList.value = res.rows;
  // total.value = res.total;
  loading.value = false;
}
 
const open = (row: any) => {
  visible.value = true;
  billInfo.value = row;
  recordList.value = row.recordList || [];
  queryParams.billId = row.id;
  
  if (props.InvoiceDetails === '开票明细') {
    fetchRecords();
  } else {
    // 初始化空行
    form.invoiceBillDetails = [{ goodsName: '', price: 0, withTaxFlag: 0, isSaved: false }];
  }
}
 
// 5. 新增行初始化
const addItemRow = () => {
  form.invoiceBillDetails.push({ 
    goodsName: '', 
    price: 0, 
    withTaxFlag: 0, 
    isSaved: false 
  });
}
const emit = defineEmits(['success']);
const handleConfirm = async () => {
  if (!formRef.value) return;
 
  // 校验一:商品明细是否全部保存,且是否有未填项
  if (form.invoiceBillDetails.length === 0) {
    proxy.$modal.msgError("请至少添加一条商品明细");
    return;
  }
 
  const hasUnsaved = form.invoiceBillDetails.some(item => !item.isSaved);
  if (hasUnsaved) {
    proxy.$modal.msgError("请先保存所有商品明细项");
    return;
  }
 
  // 校验二:表单必填项校验
  await formRef.value.validate((valid: boolean) => {
    if (valid) {
      // 构建提交数据,过滤掉 UI 使用的 isSaved 属性
      const submitData = {
        ...form,
        id: billInfo.value.id,
        invoiceBillDetails: form.invoiceBillDetails.map(({ isSaved, ...rest }) => rest)
      };
      console.log("提交数据:", submitData);
      emit('success', submitData);
      // proxy.$modal.msgSuccess("开票成功");
      // visible.value = false;
    }
  });
}
// 2. 计算本次开票金额:商品明细所有单价的和
const calculatedTotalAmount = computed(() => {
  return form.invoiceBillDetails.reduce((sum, item) => {
    return sum + (Number(item.price) || 0);
  }, 0);
});
 
// 3. 监听计算金额的变化,同步到 form 对象中(以便提交给后端)
watch(calculatedTotalAmount, (newVal) => {
  form.currentInvoicedAmount = newVal;
});
// 4. 保存单行时的校验逻辑
const saveItemRow = (row: any) => {
  if (!row.goodsName || row.goodsName.trim() === '') {
    proxy.$modal.msgError("商品名称不能为空");
    return;
  }
  row.isSaved = true;
};
const openIshpw = () => {
  visible.value = false;
}
defineExpose({ open,openIshpw })
</script>
 
<style scoped>
.mb-5 { margin-bottom: 20px; }
.section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
.section-title { font-size: 16px; font-weight: bold; border-left: 4px solid #409eff; padding-left: 10px; margin: 15px 0; }
.text-bold { font-weight: bold; }
.text-danger { color: #f56c6c; }
</style>