addInvoiceDialog.vue 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. <template>
  2. <!-- 新增发票对话框 -->
  3. <el-dialog v-model="dialog.visible" :title="dialog.title" width="800px" :before-close="handleClose">
  4. <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
  5. <!-- 发票信息 -->
  6. <el-divider content-position="left">
  7. <span style="color: #409eff; font-weight: 600">发票信息</span>
  8. </el-divider>
  9. <el-row :gutter="20">
  10. <el-col :span="12">
  11. <el-form-item label="发票类型" prop="invoiceType">
  12. <el-select v-model="form.invoiceType" placeholder="请选择发票类型" clearable style="width: 100%">
  13. <el-option v-for="dict in invoice_type" :key="dict.value" :label="dict.label" :value="dict.value" />
  14. </el-select>
  15. </el-form-item>
  16. </el-col>
  17. <el-col :span="12">
  18. <el-form-item label="发票代码" prop="invoiceCode">
  19. <el-input v-model="form.invoiceCode" placeholder="请填写发票代码" clearable />
  20. </el-form-item>
  21. </el-col>
  22. </el-row>
  23. <el-row :gutter="20">
  24. <el-col :span="12">
  25. <el-form-item label="开票公司名称" prop="invoiceCompanyName">
  26. <el-input v-model="form.invoiceCompanyName" placeholder="请填写开票公司名称" clearable />
  27. </el-form-item>
  28. </el-col>
  29. <el-col :span="12">
  30. <el-form-item label="发票金额" prop="invoiceAmount">
  31. <el-input-number v-model="form.invoiceAmount" :controls="false" clearable style="width: 100%"> </el-input-number>
  32. </el-form-item>
  33. </el-col>
  34. </el-row>
  35. <el-row :gutter="20">
  36. <el-col :span="12">
  37. <el-form-item label="开票日期" prop="invoiceDate">
  38. <el-date-picker v-model="form.invoiceDate" type="date" placeholder="请选择开票日期" style="width: 100%" value-format="YYYY-MM-DD" />
  39. </el-form-item>
  40. </el-col>
  41. </el-row>
  42. <el-row :gutter="20">
  43. <el-col :span="24">
  44. <el-form-item label="备注" prop="remark">
  45. <el-input v-model="form.remark" type="textarea" :min="2" placeholder="请填写备注" clearable />
  46. </el-form-item>
  47. </el-col>
  48. </el-row>
  49. <el-row :gutter="20">
  50. <el-col :span="24">
  51. <el-form-item label="发票附件" prop="invoiceAnnex">
  52. <el-upload
  53. :action="uploadAction"
  54. :on-success="handleUploadSuccess"
  55. :before-upload="beforeUpload"
  56. :on-remove="handleRemoveUploadFile"
  57. :on-preview="handlePreviewUploadFile"
  58. :file-list="fileList"
  59. multiple
  60. >
  61. <el-button type="primary" icon="Upload">点击上传</el-button>
  62. <template #tip>
  63. <div class="el-upload__tip">支持jpg/png/xlsx等文件,单个文件不超过50MB</div>
  64. </template>
  65. </el-upload>
  66. </el-form-item>
  67. </el-col>
  68. </el-row>
  69. </el-form>
  70. <template #footer>
  71. <div class="dialog-footer">
  72. <el-button @click="handleClose">取消</el-button>
  73. <el-button type="primary" :loading="buttonLoading" @click="handleSubmit">提交</el-button>
  74. </div>
  75. </template>
  76. </el-dialog>
  77. </template>
  78. <script setup name="AddInvoiceDialog" lang="ts">
  79. const { proxy } = getCurrentInstance() as ComponentInternalInstance;
  80. const { invoice_type } = toRefs<any>(proxy?.useDict('invoice_type'));
  81. interface InvoiceForm {
  82. invoiceType?: string;
  83. invoiceCode?: string;
  84. invoiceCompanyName?: string;
  85. invoiceAmount?: number;
  86. invoiceDate?: string;
  87. remark?: string;
  88. invoiceAnnex?: string;
  89. }
  90. const initFormData: InvoiceForm = {
  91. invoiceType: undefined,
  92. invoiceCode: undefined,
  93. invoiceCompanyName: undefined,
  94. invoiceAmount: undefined,
  95. remark: undefined,
  96. invoiceDate: undefined,
  97. invoiceAnnex: undefined
  98. };
  99. const formRef = ref<ElFormInstance>();
  100. const buttonLoading = ref(false);
  101. const form = ref<InvoiceForm>({ ...initFormData });
  102. const fileList = ref<any[]>([]);
  103. const uploadAction = import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload';
  104. const dialog = reactive<DialogOption>({
  105. visible: false,
  106. title: '发票信息'
  107. });
  108. const rules = reactive({
  109. invoiceType: [{ required: true, message: '请选择发票类型', trigger: 'change' }],
  110. invoiceCode: [{ required: true, message: '请填写发票代码', trigger: 'blur' }],
  111. invoiceCompanyName: [{ required: true, message: '请填写开票公司名称', trigger: 'blur' }],
  112. invoiceAmount: [
  113. { required: true, message: '请填写发票金额', trigger: 'blur' },
  114. {
  115. pattern: /^(0|[1-9]\d*)(\.\d{1,2})?$/ as RegExp,
  116. message: '请输入正确的金额格式,最多保留两位小数',
  117. trigger: 'blur'
  118. }
  119. ]
  120. // invoiceDate: [{ required: true, message: '请选择开票日期', trigger: 'change' }]
  121. } as any);
  122. /** 打开对话框 */
  123. const open = (data?: InvoiceForm) => {
  124. reset();
  125. if (data) {
  126. // 编辑模式
  127. Object.assign(form.value, data);
  128. // 解析附件地址并回显附件列表
  129. if (data.invoiceAnnex) {
  130. const urls = data.invoiceAnnex.split(',').filter((url) => url.trim());
  131. fileList.value = urls.map((url, index) => {
  132. const fileName = url.split('/').pop() || `附件${index + 1}`;
  133. return {
  134. name: fileName,
  135. url: url.trim(),
  136. uid: Date.now() + index,
  137. status: 'success'
  138. };
  139. });
  140. }
  141. }
  142. dialog.visible = true;
  143. };
  144. /** 重置表单 */
  145. const reset = () => {
  146. form.value = { ...initFormData };
  147. fileList.value = [];
  148. formRef.value?.clearValidate();
  149. };
  150. /** 上传前校验 */
  151. const beforeUpload = (file: any) => {
  152. const isLt50M = file.size / 1024 / 1024 < 50;
  153. if (!isLt50M) {
  154. proxy?.$modal.msgWarning('上传文件大小不能超过 50MB!');
  155. }
  156. return isLt50M;
  157. };
  158. /** 上传成功回调 */
  159. function handleUploadSuccess(response: any, file: any, fileListParam: any[]) {
  160. if (response.code === 200) {
  161. // 更新 fileList
  162. fileList.value = fileListParam;
  163. // 收集所有已上传成功的文件URL
  164. const urls = fileListParam
  165. .filter((f: any) => f.response?.code === 200 || f.url)
  166. .map((f: any) => f.response?.data?.url || f.url)
  167. .filter(Boolean);
  168. form.value.invoiceAnnex = urls.join(',');
  169. proxy?.$modal.msgSuccess('文件上传成功');
  170. } else {
  171. proxy?.$modal.msgError(response.msg || '文件上传失败');
  172. }
  173. }
  174. /** 删除文件 */
  175. const handleRemoveUploadFile = (uploadFile: any) => {
  176. form.value.invoiceAnnex = fileList.value
  177. .map((f) => f.url)
  178. .filter(Boolean)
  179. .join(',');
  180. };
  181. /** 预览文件 */
  182. const handlePreviewUploadFile = (uploadFile: any) => {
  183. if (uploadFile.url) {
  184. window.open(uploadFile.url, '_blank');
  185. } else {
  186. proxy?.$modal.msgWarning('文件地址不存在');
  187. }
  188. };
  189. /** 提交表单 */
  190. const handleSubmit = async () => {
  191. if (!formRef.value) return;
  192. await formRef.value.validate(async (valid) => {
  193. if (valid) {
  194. buttonLoading.value = true;
  195. try {
  196. // 返回发票数据给父组件
  197. emit('success', form.value);
  198. dialog.visible = false;
  199. proxy?.$modal.msgSuccess('添加成功');
  200. } catch (error) {
  201. console.error(error);
  202. } finally {
  203. buttonLoading.value = false;
  204. }
  205. }
  206. });
  207. };
  208. /** 关闭对话框 */
  209. const handleClose = () => {
  210. if (buttonLoading.value) {
  211. return;
  212. }
  213. dialog.visible = false;
  214. reset();
  215. };
  216. const emit = defineEmits(['success']);
  217. defineExpose({
  218. open
  219. });
  220. </script>
  221. <style scoped lang="scss">
  222. .dialog-footer {
  223. display: flex;
  224. justify-content: flex-end;
  225. gap: 10px;
  226. }
  227. </style>