Test.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. <template>
  2. <view>
  3. <!-- 动态生成的报销详情表单 -->
  4. <u-form>
  5. <!-- 动态生成的普通表单 -->
  6. <el-row :gutter="15">
  7. <el-col :span="24">
  8. <div class="form-grid">
  9. <div v-for="(grid, gridIndex) in gridsTest" :key="grid.key">
  10. <div v-for="(col, colIndex) in grid.columns" :key="col.key" class="form-column">
  11. <div v-for="(field, fieldIndex) in col.list" :key="field.key" class="form-field">
  12. <div v-if="field.type === 'input'" class="form-item">
  13. <text>{{ field.name }}:</text>
  14. <input
  15. v-model="formModel[field.model]"
  16. :placeholder="field.options.placeholder"
  17. :disabled="field.options.disabled"
  18. :maxlength="field.options.maxlength"
  19. :class="{'input-field': true, 'input-disabled': field.options.disabled}"
  20. />
  21. </div>
  22. <div v-if="field.type === 'textarea'" class="form-item">
  23. <text>{{ field.name }}:</text>
  24. <textarea
  25. v-model="formModel[field.model]"
  26. :placeholder="field.options.placeholder"
  27. :disabled="field.options.disabled"
  28. :rows="field.options.rows || 2"
  29. :class="{'textarea-field': true, 'textarea-disabled': field.options.disabled}"
  30. />
  31. </div>
  32. </div>
  33. </div>
  34. </div>
  35. </div>
  36. </el-col>
  37. </el-row>
  38. <el-row :gutter="15" v-for="(item, index_experience) in formModelDetail" :key="index_experience">
  39. <el-col :span="24">
  40. <u-form-item label="">
  41. <u-divider :text="'报销详情 ' + (index_experience + 1)"></u-divider>
  42. </u-form-item>
  43. </el-col>
  44. <el-col :span="24" v-for="(column, colIndex) in gridsDetail[0].tableColumns" :key="colIndex">
  45. <u-form-item :label="column.name" :prop="`formModelDetail[${index_experience}].${column.model}`">
  46. <template v-if="column.type === 'text'">
  47. <u-input v-model="formModelDetail[index_experience][column.model]" :disabled="true" placeholder="" clearable></u-input>
  48. </template>
  49. <template v-else-if="column.type === 'date'">
  50. <el-date-picker v-model="formModelDetail[index_experience][column.model]" :disabled="true" type="date" style="width:100%" size="default" placement="bottom-start" clearable></el-date-picker>
  51. </template>
  52. </u-form-item>
  53. </el-col>
  54. </el-row>
  55. <!-- 动态生成专用发票信息 -->
  56. <el-row :gutter="15" v-for="(item, invoice_index) in invoiceFormModelDetail" :key="invoice_index">
  57. <el-col :span="24">
  58. <u-form-item label="">
  59. <u-divider :text="'专用发票信息 ' + (invoice_index + 1)"></u-divider>
  60. </u-form-item>
  61. </el-col>
  62. <el-col :span="24" v-for="(column, colIndex) in gridsInvoice[0].tableColumns" :key="colIndex">
  63. <u-form-item :label="column.name" :prop="`invoiceFormModelDetail[${invoice_index}].${column.model}`">
  64. <template v-if="column.type === 'text'">
  65. <u-input v-model="invoiceFormModelDetail[invoice_index][column.model]" :disabled="true" placeholder="" clearable></u-input>
  66. </template>
  67. <template v-else-if="column.type === 'date'">
  68. <el-date-picker v-model="invoiceFormModelDetail[invoice_index][column.model]" :disabled="true" type="date" style="width:100%" size="default" placement="bottom-start" clearable></el-date-picker>
  69. </template>
  70. </u-form-item>
  71. </el-col>
  72. </el-row>
  73. <u-form-item label="附件">
  74. <el-upload
  75. class="upload-demo"
  76. :action="`http://cpaoa.xgccpm.com/api/public-modules-server/oss/file/webUpload/upload`"
  77. :on-remove="(file, fileList) => handleRemove(file, fileList,'')"
  78. :file-list="files"
  79. :on-success="(response, file, fileList) => handleUploadSuccess(response, file, fileList,'')"
  80. :limit="3">
  81. <el-button size="small" type="primary" v-if="false">点击上传</el-button>
  82. <div slot="tip" class="el-upload__tip">只能上传不超过 3 个文件</div>
  83. <template slot="file" slot-scope="{ file }" v-if="shouldShowFile(file)">
  84. <span @click="handleFileClick(file)">{{ file.name }}</span>
  85. <!-- <el-button type="text" @click="handleDelete(file,'')"> ✕ </el-button>-->
  86. </template>
  87. </el-upload>
  88. </u-form-item>
  89. </u-form>
  90. </view>
  91. </template>
  92. <script>
  93. import OSSService from "@/api/sys/OSSService"
  94. export default {
  95. name: 'DynamicForm',
  96. props: {
  97. grids: {
  98. type: Array,
  99. required: true
  100. },
  101. },
  102. data() {
  103. return {
  104. gridsTest: [],
  105. gridsDetail: [],
  106. gridsInvoice: [],
  107. formModel: {},
  108. formModelDetail: [],
  109. invoiceFormModelDetail: [],
  110. files: [],
  111. showFileList: []
  112. };
  113. },
  114. ossService: null,
  115. created() {
  116. this.ossService = new OSSService()
  117. },
  118. watch: {
  119. grids: {
  120. handler(newVal) {
  121. this.initializeFormModel();
  122. },
  123. deep: true,
  124. immediate: true
  125. }
  126. },
  127. methods: {
  128. initializeFormModel() {
  129. this.files = this.grids.files
  130. if (this.files) {
  131. this.files.forEach( (item,index) => {
  132. this.$set(this.showFileList, index, true);
  133. })
  134. }
  135. this.formModel = {};
  136. for (let i = 0; i < this.grids.length; i++) {
  137. const grid = this.grids[i];
  138. //获取报销详情前的基本信息数据
  139. if (this.isNotEmpty(grid.name)) {
  140. // 切掉数组中从当前索引开始的所有元素
  141. this.gridsTest = this.grids.slice(0, i);
  142. break;
  143. }
  144. }
  145. this.gridsTest.forEach(grid => {
  146. grid.columns.forEach(col => {
  147. col.list.forEach(field => {
  148. this.$set(this.formModel, field.model, field.options.defaultValue || '');
  149. });
  150. });
  151. });
  152. this.getReimbursementDetails();
  153. this.getInvoiceDetails();
  154. },
  155. //报销详情数据处理
  156. getReimbursementDetails() {
  157. this.formModelDetail = [];
  158. let startIndex = -1;
  159. let endIndex = -1;
  160. // 找到 "报销详情" 和 "专用发票信息" 的索引
  161. for (let i = 0; i < this.grids.length; i++) {
  162. const grid = this.grids[i];
  163. if (grid.name === "报销详情") {
  164. startIndex = i;
  165. }
  166. if (grid.name === "专用发票信息") {
  167. endIndex = i;
  168. break;
  169. }
  170. }
  171. // 确保找到的索引是有效的,并且 startIndex 在 endIndex 之前
  172. if (startIndex !== -1 && endIndex !== -1 && startIndex < endIndex) {
  173. // 提取 startIndex 和 endIndex 之间的数据(不包括 "报销详情" 和 "专用发票信息" 本身)
  174. this.gridsDetail = this.grids.slice(startIndex + 1, endIndex);
  175. // 初始化 formModelDetail
  176. const defaultValueList = this.gridsDetail[0].options.defaultValue || [];
  177. defaultValueList.forEach((defaultValue, index_experience) => {
  178. const data = {};
  179. this.gridsDetail.forEach(grid => {
  180. grid.tableColumns.forEach(column => {
  181. if (defaultValue.hasOwnProperty(column.model)) {
  182. this.$set(data, column.model, defaultValue[column.model] || '');
  183. }
  184. });
  185. });
  186. this.$set(this.formModelDetail, index_experience, data);
  187. });
  188. } else {
  189. // 如果没有找到有效的索引范围,清空 gridsDetail
  190. this.gridsDetail = [];
  191. }
  192. },
  193. // 发票详情数据处理
  194. getInvoiceDetails() {
  195. this.invoiceFormModelDetail = [];
  196. let endIndex = -1;
  197. // 找到 "专用发票信息" 的索引
  198. for (let i = 0; i < this.grids.length; i++) {
  199. const grid = this.grids[i];
  200. if (grid.name === "专用发票信息") {
  201. endIndex = i;
  202. break;
  203. }
  204. }
  205. // 确保找到的索引是有效的,并且 startIndex 在 endIndex 之前
  206. if (endIndex !== -1 && endIndex < this.grids.length ) {
  207. // 提取 startIndex 和 endIndex 之间的数据(不包括 "报销详情" 和 "专用发票信息" 本身)
  208. this.gridsInvoice = this.grids.slice(endIndex + 1, this.grids.length);
  209. // 初始化 invoiceFormModelDetail
  210. const defaultValueList = this.gridsInvoice[0].options.defaultValue || [];
  211. defaultValueList.forEach((defaultValue, invoice_index) => {
  212. const data = {};
  213. this.gridsInvoice.forEach(grid => {
  214. grid.tableColumns.forEach(column => {
  215. if (defaultValue.hasOwnProperty(column.model)) {
  216. this.$set(data, column.model, defaultValue[column.model] || '');
  217. }
  218. });
  219. });
  220. this.$set(this.invoiceFormModelDetail, invoice_index, data);
  221. });
  222. } else {
  223. // 如果没有找到有效的索引范围,清空 gridsInvoice
  224. this.gridsInvoice = [];
  225. }
  226. },
  227. isEmpty(value) {
  228. let result = false;
  229. if (value == null || value == undefined) {
  230. result = true;
  231. }
  232. if (typeof value == 'string' && (value.replace(/\s+/g, "") == "" || value == "")) {
  233. result = true;
  234. }
  235. if (typeof value == "object" && value instanceof Array && value.length === 0) {
  236. result = true;
  237. }
  238. return result;
  239. },
  240. isNotEmpty(value) {
  241. return !this.isEmpty(value);
  242. },
  243. shouldShowFile(file) {
  244. if (this.files && this.files.length > 0) {
  245. // 返回一个布尔值,确定是否显示上传成功后的文件
  246. return this.showFileList[this.files.indexOf(file)];
  247. }
  248. return false; // 默认返回 false 或者其他适当的
  249. },
  250. async handleFileClick(file) {
  251. await this.ossService.getTemporaryUrl(file.url).then((data) => {
  252. file.lsUrl = data
  253. })
  254. if (this.isImage(file.name)) {
  255. // 如果是图片文件,则执行放大显示图片的逻辑
  256. this.handleImageClick(file.lsUrl);
  257. } else {
  258. // window.open(file.lsUrl, '_blank')
  259. window.location.href = file.lsUrl
  260. // 如果不是图片文件,则执行其他操作,比如下载文件等
  261. }
  262. },
  263. // 判断文件是否是图片类型
  264. isImage(fileName) {
  265. const ext = fileName.toLowerCase().split('.').pop(); // 获取文件的后缀名
  266. return ['jpg', 'jpeg', 'png', 'gif', 'bmp'].includes(ext); // 判断后缀名是否是图片类型
  267. },
  268. handleImageClick(url) {
  269. let urls = [url]
  270. // 大图预览
  271. uni.previewImage({
  272. current: url,
  273. urls: urls,
  274. })
  275. // 在点击图片时执行放大显示的逻辑
  276. // this.$alert(`<img src="${file.lsUrl}" style="max-width: 100%; max-height: 100%;" />`, '图片详情', {
  277. // dangerouslyUseHTMLString: true,
  278. // customClass: 'custom-alert'
  279. // });
  280. },
  281. handleDelete(file) {
  282. // 处理删除文件的逻辑
  283. // 从文件列表中移除文件
  284. const index = this.files.indexOf(file);
  285. if (index !== -1) {
  286. this.files.splice(index, 1);
  287. this.showFileList.splice(index, 1); // 从showFileList中移除对应的元素
  288. }
  289. },
  290. }
  291. };
  292. </script>
  293. <style scoped>
  294. .form-grid {
  295. margin-bottom: 10px; /* 减少外边距 */
  296. }
  297. .form-column {
  298. display: flex;
  299. flex-direction: column;
  300. margin-bottom: 10px; /* 减少外边距 */
  301. }
  302. .form-field {
  303. margin-bottom: 10px; /* 减少外边距 */
  304. }
  305. .form-item {
  306. display: flex;
  307. flex-direction: column;
  308. }
  309. .input-field, .textarea-field {
  310. width: 100%;
  311. height: 30px;
  312. border: 1px solid #ccc;
  313. border-radius: 4px;
  314. }
  315. .textarea-field {
  316. height: 60px;
  317. }
  318. /* 禁用状态样式 */
  319. .input-disabled {
  320. background-color: #f5f5f5;
  321. border-color: #dcdcdc;
  322. cursor: not-allowed;
  323. }
  324. .textarea-disabled {
  325. background-color: #f5f5f5;
  326. border-color: #dcdcdc;
  327. cursor: not-allowed;
  328. }
  329. /* 解决父级容器的内边距问题 */
  330. .upload-demo {
  331. margin-top: 10px; /* 增加间距 */
  332. }
  333. </style>